Runtime: إضافة PriorityQueue<t>إلى المجموعات</t>

تم إنشاؤها على ٣٠ يناير ٢٠١٥  ·  318تعليقات  ·  مصدر: dotnet/runtime

راجع أحدث اقتراح في corefxlab repo.

خيارات الاقتراح الثاني

اقتراح من https://github.com/dotnet/corefx/issues/574#issuecomment -307971397

الافتراضات

العناصر الموجودة في قائمة انتظار الأولوية فريدة. إذا لم تكن كذلك ، فسنضطر إلى تقديم "مقابض" للعناصر لتمكين التحديث / الإزالة. أو يجب أن تنطبق دلالات التحديث / الإزالة على أول / كل شيء ، وهذا أمر غريب.

على غرار Queue<T> ( رابط MSDN )

API

ج #
فئة عامة أولوية
: I عدد لا يحصى ،
I عدد لا يحصى <(عنصر TElement ، أولوية الأولوية)> ،
مجموعة IReadOnlyCollection <(عنصر TElement ، أولوية الأولوية)>
// لم يتم تضمين المجموعة عمدًا
{
public PriorityQueue ()؛
public PriorityQueue (IComparerالمقارنة) ؛

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

//
// جزء المحدد
//
public PriorityQueue (Funcالأفضلية) ؛
public PriorityQueue (Funcمحدد الأولوية ، IComparerالمقارنة) ؛

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

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

}
""

أسئلة مفتوحة:

  1. اسم الفئة PriorityQueue مقابل Heap
  2. تقديم IHeap وتحميل المُنشئ الزائد؟ (هل ننتظر لاحقًا؟)
  3. تقديم IPriorityQueue ؟ (هل يجب أن ننتظر لاحقًا - مثال IDictionary )
  4. استخدم المحدد (للأولوية المخزنة داخل القيمة) أم لا (اختلاف 5 واجهات برمجة التطبيقات)
  5. استخدم tuples (TElement element, TPriority priority) مقابل KeyValuePair<TPriority, TElement>

    • هل يجب أن يكون لدى Peek و Dequeue بدلاً من out وسيطة بدلاً من tuple؟

  6. هل رمي Peek و Dequeue مفيد على الإطلاق؟

الاقتراح الأصلي

طلبت المشكلة https://github.com/dotnet/corefx/issues/163 إضافة قائمة انتظار الأولوية إلى هياكل بيانات مجموعة .NET الأساسية.

هذا المنشور ، على الرغم من كونه مكررًا ، يهدف إلى العمل على الإرسال الرسمي إلى معالجة مراجعة واجهة برمجة تطبيقات corefx. محتويات الإصدار هي _speclet_ لنظام جديد System.Collections.Generic.PriorityQueueنوع.

سأساهم في العلاقات العامة ، إذا تمت الموافقة عليه.

الأساس المنطقي والاستخدام

تفتقر مكتبات فئة .NET Base Class (BCL) حاليًا إلى دعم مجموعات المنتج والمستهلك المطلوبة. من المتطلبات الشائعة للعديد من تطبيقات البرامج القدرة على إنشاء قائمة بالعناصر بمرور الوقت ومعالجتها بترتيب مختلف عن الترتيب الذي تم استلامها به.

توجد ثلاثة هياكل بيانات عامة داخل التسلسل الهرمي System.Collections لمساحات الأسماء التي تدعم مجموعة مفروزة من العناصر ؛ System.Collections.Generic.SortedList و System.Collections.Generic.SortedSet و System.Collections.Generic.SortedDictionary.

من بينها ، SortedSet و SortedDictionary غير مناسبين لأنماط المنتج والمستهلك التي تولد قيمًا مكررة. تعقيد SortedList هو Θ (n) أسوأ حالة لكل من الإضافة والإزالة.

تعد بنية البيانات الأكثر كفاءة للذاكرة والوقت للمجموعات المطلوبة مع أنماط استخدام المنتج والمستهلك قائمة انتظار ذات أولوية. بخلاف عندما يكون تغيير حجم السعة ضروريًا ، يكون أداء إدخال الحالة الأسوأ (قائمة الانتظار) وإزالة أعلى (dequeue) هو Θ (log n) - أفضل بكثير من الخيارات الحالية الموجودة في BCL.

تتمتع قوائم انتظار الأولوية بدرجة كبيرة من قابلية التطبيق عبر فئات مختلفة من التطبيقات. تقدم صفحة Wikipedia الموجودة في Priority Queues قائمة بالعديد من حالات الاستخدام المختلفة التي يتم فهمها جيدًا. في حين أن عمليات التنفيذ عالية التخصص قد لا تزال تتطلب عمليات تنفيذ مخصصة لقائمة الانتظار ذات الأولوية ، فإن التنفيذ القياسي سيغطي نطاقًا واسعًا من سيناريوهات الاستخدام. تعد قوائم انتظار الأولوية مفيدة بشكل خاص في جدولة إنتاج العديد من المنتجين ، وهو نمط مهم في البرامج المتوازية للغاية.

تجدر الإشارة إلى أن كلاً من مكتبة C ++ القياسية و Java يقدمان وظائف قائمة انتظار ذات أولوية كجزء من واجهات برمجة التطبيقات الأساسية الخاصة بهم.

API المقترحة

ج #
System.Collections.Generic. مساحة الاسم
{
///


/// يمثل مجموعة من العناصر التي تمت إزالتها بترتيب مصنف.
///

///يحدد نوع العناصر في قائمة الانتظار.
[DebuggerDisplay ("Count = {count}")]
[DebuggerTypeProxy (typeof (System_PriorityQueueDebugView <>))]
فئة عامة أولوية: I لا يحصى، ICollection، IEnumerable، IReadOnlyCollection
{
///
/// تهيئة مثيل جديد من صف دراسي
/// الذي يستخدم المقارنة الافتراضية.
///

public PriorityQueue ()؛

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

}
""

تفاصيل

  • ستكون بنية بيانات التنفيذ عبارة عن كومة ثنائية. سيتم إرجاع العناصر ذات قيمة المقارنة الأكبر أولاً. (تنازليا)
  • تعقيدات الوقت:

| العملية | التعقيد | ملاحظات |
| --- | --- | --- |
| بناء | Θ (1) | |
| إنشاء باستخدام IEnumerable | Θ (ن) | |
| قائمة الانتظار | Θ (تسجيل ن) | |
| ديكيو | Θ (تسجيل ن) | |
| نظرة خاطفة | Θ (1) | |
| العد | Θ (1) | |
| مسح | Θ (ن) | |
| يحتوي على | Θ (ن) | |
| نسخ إلى | Θ (ن) | استخدامات Array.Copy ، قد يكون التعقيد الفعلي أقل |
| ToArray | Θ (ن) | استخدامات Array.Copy ، قد يكون التعقيد الفعلي أقل |
| GetEnumerator | Θ (1) | |
| العداد Θ (1) | |

  • الأحمال الزائدة للمنشئ الإضافية التي تأخذ System.Comparisonمندوب تم حذفه عن قصد لصالح مساحة سطح API مبسطة. يمكن للمتصلين استخدام المقارنةأنشئ لتحويل دالة أو تعبير Lambda إلى IComparerواجهة إذا لزم الأمر. هذا يتطلب من المتصل أن يتحمل تخصيص كومة لمرة واحدة.
  • على الرغم من أن System.Collections.Generic لم تصبح بعد جزءًا من corefx ، أقترح إضافة هذه الفئة إلى corefxlab في هذه الأثناء. يمكن نقله إلى مستودع corefx الأساسي بمجرد إضافة System.Collections.Generic وهناك إجماع على أنه يجب رفع حالته من التجريبية إلى API الرسمية.
  • لم يتم تضمين خاصية IsEmpty ، نظرًا لعدم وجود عقوبة أداء إضافية عند استدعاء Count. لا تتضمن غالبية هياكل بيانات المجموعة IsEmpty.
  • تم تنفيذ خصائص IsSynchronized و SyncRoot الخاصة بـ ICollection بشكل صريح لأنها عفا عليها الزمن بشكل فعال. يتبع هذا أيضًا النمط المستخدم لهياكل البيانات System.Collection.G العامة الأخرى.
  • Dequeue و Peek يطرحان InvalidOperationException عندما تكون قائمة الانتظار فارغة لمطابقة السلوك المحدد لـ System.Collections.Queue.
  • IProducerConsumerCollectionلم يتم تنفيذه حيث تنص وثائقه على أنه مخصص فقط للمجموعات ذات الخيط الآمن.

أسئلة مفتوحة

  • هل يعد تجنب تخصيص كومة إضافية أثناء المكالمات إلى GetEnumerator عند استخدام foreach سببًا قويًا بما يكفي لتضمين بنية العداد العام المتداخلة؟
  • هل يجب على CopyTo و ToArray و GetEnumerator إرجاع النتائج بترتيب ذي أولوية (تم فرزها) أو الترتيب الداخلي الذي تستخدمه بنية البيانات؟ أفترض أنه يجب إعادة الطلب الداخلي ، لأنه لا يتحمل أي عقوبات أداء إضافية. ومع ذلك ، فهذه مشكلة محتملة تتعلق بقابلية الاستخدام إذا اعتبر المطور أن الفئة "قائمة انتظار مصنفة" بدلاً من قائمة انتظار ذات أولوية.
  • هل تؤدي إضافة نوع يسمى PriorityQueue إلى System.Collections.Generic إلى حدوث تغيير محتمل؟ يتم استخدام مساحة الاسم بكثرة ، وقد تتسبب في حدوث مشكلة توافق المصدر للمشاريع التي تتضمن نوع قائمة انتظار الأولوية الخاصة بها.
  • هل يجب فصل العناصر بترتيب تصاعدي أو تنازلي ، بناءً على ناتج IComparer؟ (افتراضي هو ترتيب تصاعدي ، لمطابقة اصطلاح الفرز العادي لـ IComparer).
  • هل يجب أن تكون المجموعة "مستقرة"؟ بمعنى آخر ، هل يجب أن يكون هناك عنصران متساويان في المقارنةيتم فصل النتائج في قائمة الترتيب نفسها التي تم وضعها في قائمة الانتظار بالضبط؟ (افتراضي هو أن هذا ليس مطلوبًا)

    التحديثات

  • تم إصلاح تعقيد عبارة "إنشاء باستخدام IEnumerable" إلى Θ (n). شكراsvick.

  • تمت إضافة سؤال خيار آخر بخصوص ما إذا كان ينبغي ترتيب قائمة انتظار الأولوية بترتيب تصاعدي أو تنازلي مقارنة بمقارن IC.
  • تمت إزالة NotSupportedException من خاصية SyncRoot الصريحة لمطابقة سلوك الأنواع الأخرى System.Collection.Generic بدلاً من استخدام النمط الأحدث.
  • جعل طريقة GetEnumerator العامة تُرجع بنية Enumerator متداخلة بدلاً من IEnumerable، على غرار الأنواع الموجودة System.Collections.Generic. هذا تحسين لتجنب تخصيص الكومة (GC) عند استخدام حلقة foreach.
  • تمت إزالة سمة ComVisible.
  • تم تغيير التعقيد من Clear إلى Θ (n). شكرا mbeidler.
api-needs-work area-System.Collections wishlist

التعليق الأكثر فائدة

بنية البيانات الكومة أمر لا بد منه لعمل leetcode
المزيد من leetcode ، والمزيد من المقابلات البرمجية c # مما يعني المزيد من مطوري c #.
المزيد من المطورين يعني نظام بيئي أفضل.
نظام بيئي أفضل يعني أنه إذا كان لا يزال بإمكاننا البرمجة في c # غدًا.

باختصار: هذه ليست ميزة فحسب ، بل هي أيضًا المستقبل. هذا هو السبب في أن القضية تسمى "المستقبل".

ال 318 كومينتر

| العملية | التعقيد |
| --- | --- |
| إنشاء باستخدام IEnumerable | Θ (تسجيل ن) |

أعتقد أن هذا يجب أن يكون Θ (n). تحتاج على الأقل إلى تكرار الإدخال.

+1

تتمتع Rx بفئة انتظار ذات أولوية عالية تم اختبارها على مستوى الإنتاج:

https://github.com/Reactive-Extensions/Rx.NET/blob/master/Rx.NET/Source/System.Reactive.Core/Reactive/Internal/PriorityQueue.cs

هل ينبغي استخدام بنية العداد العامة المتداخلة لتجنب تخصيص كومة إضافية أثناء المكالمات إلى GetEnumerator وعند استخدام foreach؟ افتراضي هو لا ، لأن التعداد على قائمة انتظار هو عملية غير شائعة.

أميل إلى استخدام عداد البنية ليكون متسقًا مع Queue<T> الذي يستخدم عدادًا هيكليًا. أيضًا ، إذا لم يتم استخدام عداد البنية الآن ، فلن نتمكن من تغيير PriorityQueue<T> لاستخدامه في المستقبل.

ربما أيضًا طريقة لإدراج الدُفعات؟ هل يمكن دائمًا عندئذٍ الفرز والاستمرار من نقطة الإدراج السابقة بدلاً من البدء في البداية إذا كان ذلك سيساعد ؟:

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

لقد رميت نسخة من التطبيق الأولي هنا . تغطية الاختبار لم تكتمل بعد ، ولكن إذا كان لدى أي شخص فضول ، فيرجى إلقاء نظرة وإخبارنا برأيك. لقد حاولت اتباع اصطلاحات الترميز الحالية من فئات System.Collections بقدر الإمكان.

رائع. بعض الملاحظات الأولية:

  • ينفذ Queue<T>.Enumerator IEnumerator.Reset صراحةً. هل يجب أن يفعل PriorityQueue<T>.Enumerator الشيء نفسه؟
  • يستخدم Queue<T>.Enumerator _index == -2 للإشارة إلى التخلص من العداد. PriorityQueue<T>.Enumerator له نفس التعليق لكن لديه حقل _disposed إضافي. ضع في اعتبارك التخلص من الحقل الإضافي _disposed واستخدم _index == -2 للإشارة إلى أنه قد تم جعله أصغر حجمًا وأن يكون متسقًا مع Queue<T>
  • أعتقد أنه يمكن إزالة الحقل _emptyArray الثابت واستبدال الاستخدام بـ Array.Empty<T>() بدلاً من ذلك.

أيضا...

  • المجموعات الأخرى التي تأخذ مقارنة (على سبيل المثال ، Dictionary<TKey, TValue> ، HashSet<T> ، SortedList<TKey, TValue> ، SortedDictionary<TKey, TValue> ، SortedSet<T> ، إلخ.) تسمح بأن تكون فارغة تم تمريره للمقارنة ، وفي هذه الحالة يتم استخدام Comparer<T>.Default .

أيضا...

  • يمكن تحسين ToArray بالتحقق من _size == 0 قبل تخصيص المصفوفة الجديدة ، وفي هذه الحالة فقط قم بإرجاع Array.Empty<T>() .

justinvp ردود فعل كبيرة ، شكرا!

  • لقد قمت بتطبيق Enumeratorأعد التعيين بشكل صريح نظرًا لوظائفها الأساسية غير المهملة للعداد. سواء تم الكشف عنه أم لا يبدو غير متسق عبر أنواع المجموعات ، والقليل فقط يستخدم المتغير الصريح.
  • تمت إزالة الحقل _disposed لصالح _index ، شكرًا! ألقيت ذلك في اللحظة الأخيرة في تلك الليلة وفاتت ما هو واضح. قررت الاحتفاظ بـ ObjectDisposedException من أجل صحتها مع أنواع المجموعات الأحدث ، على الرغم من أن System.Collections.Generic القديمة لا تستخدمها.
  • مصفوفة فارغةهي ميزة F # ، لذا لا يمكنك استخدامها هنا للأسف!
  • عدّل معلمات المقارنة لقبول اكتشاف فارغ وجيد!
  • يعد تحسين ToArray أمرًا صعبًا. _Technical_ المصفوفات الناطقة هي قابلة للتغيير في C # ، حتى عندما يكون طولها صفر. في الواقع ، أنت على حق ، التخصيص ليس ضروريًا ويمكن تحسينه. أنا أميل إلى تنفيذ أكثر حذراً ، في حالة وجود آثار جانبية لا أفكر فيها. من الناحية المعنوية ، سيظل المتصل يتوقع هذا التخصيص ، وهو تخصيص ثانوي.

تضمين التغريدة

Array.Empty هي ميزة F # ، لذا لا يمكنك استخدامها هنا للأسف!

ليس بعد الآن: https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Array.cs#L1060 -L1069

لقد قمت بترحيل الكود إلى

نفذت المصفوفة فارغة() قم بتغيير كل شيء وتوصيله بخط أنابيب البناء العادي. كان أحد الأخطاء المؤقتة الطفيفة التي كان عليّ تقديمها هو امتلاك مشروع System.Collections يعتمد على System.Collections nuget package ، كمقارنليست مفتوحة المصدر بعد.

سيتم إصلاحه بمجرد اكتمال إصدار dotnet / corefx # 966.

أحد المجالات الرئيسية التي أبحث عنها هو كيفية التعامل مع ToArray و CopyTo والعداد. تم تحسينها حاليًا للأداء ، مما يعني أن المصفوفة الهدف عبارة عن كومة ولا يتم فرزها بواسطة المقارنة.

هناك ثلاثة خيارات:
1) اتركها كما هي ، واستند إلى عدم فرز المصفوفة التي تم إرجاعها. (إنها قائمة انتظار ذات أولوية ، وليست قائمة انتظار تم فرزها)
2) تعديل طرق فرز العناصر / المصفوفات المرتجعة. لن تكون عمليات O (n) بعد الآن.
3) إزالة الأساليب والدعم العددي تماما. هذا هو الخيار "النقي" ولكنه يزيل القدرة على انتزاع العناصر المتبقية في قائمة الانتظار بسرعة عندما لا تكون هناك حاجة إلى قائمة انتظار الأولوية.

شيء آخر أود الحصول على تعليقات عليه هو ما إذا كان يجب أن تكون قائمة الانتظار مستقرة لعنصرين لهما نفس الأولوية (مقارنة نتيجة 0). بشكل عام ، لا تقدم قوائم انتظار الأولوية أي ضمانات بأن عنصرين لهما نفس الأولوية سيتم فصلهما في الترتيب حسب ترتيب إدراجهما في قائمة الانتظار ، لكنني لاحظت أن تنفيذ قائمة انتظار الأولوية الداخلية المستخدم في System.Reactive.Core استغرق بعض النفقات الإضافية لضمان هذه الخاصية. أفضل عدم القيام بذلك ، لكنني لست متأكدًا تمامًا من الخيار الأفضل من حيث توقعات المطورين.

لقد حصلت على هذا العلاقات العامة لأنني كنت مهتمًا أيضًا بإضافة قائمة انتظار ذات أولوية إلى .NET. يسعدني أن أرى شخصًا ما بذل جهدًا لتقديم هذا الاقتراح :). بعد مراجعة الكود لاحظت ما يلي:

  • عندما يكون الطلب IComparer غير متسق مع Equals ، قد يكون سلوك تنفيذ Contains (الذي يستخدم IComparer ) مفاجئًا لبعض المستخدمين ، حيث أنها تحتوي أساسًا على عنصر له أولوية متساوية.
  • لم أرَ رمزًا لتقليص المصفوفة في Dequeue . تقليص مجموعة الكومة بمقدار النصف عندما يكون الربع ممتلئًا أمرًا نموذجيًا.
  • يجب على Enqueue يقبل أسلوب null الحجج؟
  • أعتقد أن درجة تعقيد Clear يجب أن تكون Θ (n) ، نظرًا لأن هذا هو تعقيد System.Array.Clear ، والذي يستخدمه. https://msdn.microsoft.com/en-us/library/system.array.clear٪28v=vs.110٪29.aspx

لم أرَ رمزًا لتقليص المصفوفة في Dequeue . تقليص مجموعة الكومة بمقدار النصف عندما يكون الربع ممتلئًا أمرًا نموذجيًا.

Queue<T> و Stack<T> أيضًا بتقليص المصفوفات الخاصة بهم (استنادًا إلى المصدر المرجعي ، فهم ليسوا في CoreFX حتى الآن).

mbeidler كنت قد فكرت في إضافة شكل من أشكال تقلص الصفيف التلقائي في dequeue ، ولكن كما أوضح svick أنه غير موجود في التطبيقات المرجعية لهياكل البيانات المماثلة. يسعدني أن أسمع من أي شخص في فريق .NET Core / BCL ما إذا كان هناك أي سبب معين لاختياره هذا النمط من التنفيذ.

تحديث: راجعت القائمة، طابورو Queue و ArrayList - لا يقوم أي منهم بتقليص حجم المصفوفة الداخلية عند إزالة / dequeue.

يجب أن يدعم Enqueue القيم الخالية ، ويتم توثيقه على أنه يسمح لها. هل واجهت حشرة؟ لا أتذكر مدى قوة اختبارات الوحدة في المنطقة حتى الآن.

مثير للاهتمام ، لقد لاحظت في مصدر المرجع المرتبط بواسطة Queue<T> له ثابت خاص غير مستخدم اسمه _ShrinkThreshold . ربما كان هذا السلوك موجودًا في إصدار سابق.

بخصوص استخدام IComparer بدلاً من Equals في تنفيذ Contains ، كتبت اختبار الوحدة التالي ، والذي سيفشل حاليًا: https: //gist.github. كوم / mbeidler / 9e9f566ba7356302c57e

mbeidler نقطة جيدة. وفقًا لـ MSDN و IComparer/ IC مقارنةيضمن فقط أن قيمة الصفر لها نفس ترتيب الفرز.

ومع ذلك ، يبدو أن نفس المشكلة موجودة في فئات المجموعات الأخرى. إذا قمت بتعديل الكود للعمل على SortedList، حالة الاختبار ما زالت تفشل على ContainsKey. تنفيذ SortedListيحتوي .ContainsKey على استدعاء Array.BinarySearch ، والذي يعتمد على IComparer للتحقق من المساواة. وينطبق الشيء نفسه على SortedSet.

يمكن القول إنه خطأ في فئات المجموعة الحالية أيضًا. سأقوم بالبحث في بقية فئات المجموعات ومعرفة ما إذا كانت هناك أي هياكل بيانات أخرى تقبل IComparer ولكنها تختبر المساواة بشكل منفصل. أنت على حق ، بالنسبة لقائمة انتظار ذات أولوية ، تتوقع سلوك ترتيب مخصص مستقل تمامًا عن المساواة.

قمت بإصلاح وحالة الاختبار في فرع الشوكة الخاص بي. يعتمد التنفيذ الجديد لـ "يحتوي على" مباشرةً على سلوك "القائمة".يتضمن. منذ القائمةلا يقبل IEqualityComparer ، السلوك مكافئ وظيفيًا.

عندما أحصل على بعض الوقت لاحقًا اليوم ، سأرسل على الأرجح تقارير الأخطاء للمجموعات المضمنة الأخرى. ربما لا يمكن إصلاحه بسبب سلوك الانحدار ، ولكن على الأقل تحتاج الوثائق إلى التحديث.

وأعتقد أنه من المنطقي ل ContainsKey لاستخدام IComparer<TKey> التنفيذ، لأن ذلك هو ما يحدد مفتاح. ومع ذلك ، أعتقد أنه سيكون من المنطقي أكثر أن تستخدم ContainsValue Equals بدلاً من IComparable<TValue> في بحثها الخطي ؛ على الرغم من أنه في هذه الحالة يتم تقليل النطاق بشكل كبير نظرًا لأن الترتيب الطبيعي للنوع من غير المرجح أن يكون غير متوافق مع المساواة.

يبدو أنه في وثائق MSDN لـ SortedList<TKey, TValue> ، يشير قسم الملاحظات لـ ContainsValue إلى أن ترتيب فرز TValue يُستخدم بدلاً من المساواة.

terrajobst ما هو شعورك حيال اقتراح API حتى الآن؟ هل تشعر أن هذا مناسب تمامًا لـ CoreFX؟

: +1:

شكرا لتقديم هذا. أعتقد أن لدينا بيانات كافية لإجراء مراجعة رسمية لهذا الاقتراح ، ومن ثم صنفته على أنه "جاهز لمراجعة واجهة برمجة التطبيقات"

نظرًا لأن Dequeue و Peek هما طريقتان للرمي يحتاج المتصل إلى التحقق من العد قبل كل مكالمة. هل من المنطقي بدلاً من ذلك (أو بالإضافة إلى ذلك) تقديم TryDequeue و TryPeek باتباع نمط المجموعات المتزامنة؟ هناك مشكلات مفتوحة لإضافة طرق غير رمي إلى المجموعات العامة الحالية ، لذا فإن إضافة مجموعة جديدة لا تحتوي على هذه الأساليب يبدو أنها تأتي بنتائج عكسية.

andrewgmorris ذات الصلة https://github.com/dotnet/corefx/issues/4316 "إضافة TryDequeue إلى قائمة الانتظار"

لقد أجرينا مراجعة أساسية لهذا واتفقنا على أننا نريد ProrityQueue في إطار العمل. ومع ذلك ، نحتاج إلى الحصول على شخص ما للمساعدة في دفع التصميم والتنفيذ لذلك. يمكن لمن يكتشف المشكلة أن يعمل

إذن ما هو العمل المتبقي على API؟

هذا مفقود من اقتراح واجهة برمجة التطبيقات أعلاه: يجب أن يطبق PriorityQueue<T> IReadOnlyCollection<T> لمطابقة Queue<T> ( Queue<T> الآن ينفذ IReadOnlyCollection<T> كـ .NET 4.6).

لا أعلم أن قوائم الانتظار ذات الأولوية المستندة إلى الصفيف هي الأفضل. تخصيص الذاكرة في .NET سريع حقًا. ليس لدينا نفس مشكلة الكتل الصغيرة للبحث التي تعامل معها malloc القديم. نرحب بك لاستخدام رمز قائمة انتظار الأولوية الخاص بي من هنا: https://github.com/BrannonKing/Kts.Astar/tree/master/Kts.AStar

ebickle واحد صغير nit على _speclet_. انها تقول:

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

ألا يجب أن تقول بدلاً من ذلك ، /// Inserts object into the <see cref="PriorityQueue{T}"> by its priority.

SunnyWar إصلاح توثيق طريقة Enqueue ، شكرًا لك!

منذ بعض الوقت ، قمت بإنشاء بنية بيانات ذات تعقيدات مشابهة لقائمة انتظار الأولوية بناءً على بنية بيانات قائمة التخطي التي قررت في هذه المرحلة مشاركتها: https://gist.github.com/bbarry/5e0f3cc1ac7f7521fe6ea25947f48ace

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

تتطابق قائمة التخطي مع تعقيدات قائمة انتظار الأولوية أعلاه في الحالات المتوسطة ، باستثناء أن تحتوي على حالة متوسطة O(log(n)) . بالإضافة إلى ذلك ، فإن الوصول إلى أي من العناصر الأولى أو الأخيرة عبارة عن عمليات زمنية ثابتة والتكرار بالترتيب الأمامي والعكسي يتطابق مع تعقيدات الأمر الآجل لـ PQ.

من الواضح أن هناك جوانب سلبية لمثل هذا الهيكل في شكل تكاليف أعلى على الذاكرة ، وهو ينتقل إلى O(n) أسوأ حالة يتم إدراجها وإزالتها ، لذلك لها مفاضلاتها ...

هل هذا مطبق بالفعل في مكان ما؟ ما هو الإصدار المتوقع؟
ماذا أيضًا عن تحديث أولوية عنصر موجود؟

@ianhays Priya91 هذا استعداد للحصول على تميز جاهزة للمراجعة؟

هذا مفقود من اقتراح API أعلاه: PriorityQueueيجب أن تنفذ IReadOnlyCollectionلمطابقة قائمة الانتظار(طابورالآن تنفذ IReadOnlyCollectionبدءًا من .NET 4.6).

أنا أتفق مع justinvp هنا.

@ianhays Priya91 هذا استعداد للحصول على تميز جاهزة للمراجعة؟

سأقول ذلك. كان هذا جالسًا لفترة من الوقت ؛ دعونا نتحرك عليه.

ianhaysjustinvp لقد تحديث المواصفات لتنفيذ IReadOnlyCollection. شكرا لك!

لدي تطبيق كامل للفصل ومرتبط به PriorityQueueDebugView الذي يستخدم تطبيقًا قائمًا على المصفوفة. لم يتم تغطية اختبارات الوحدة بنسبة 100٪ حتى الآن ، ولكن إذا كان هناك بعض الاهتمام ، يمكنني القيام ببعض العمل وإزالة الغبار عن الشوكة.

أوضحNKnusperer نقطة جيدة حول تحديث أولوية عنصر موجود. سأتركه الآن ولكن هذا شيء يجب مراعاته أثناء مراجعة المواصفات.

هناك تطبيقان في الإطار الكامل ، وهما بدائل محتملة.
https://referencesource.microsoft.com/#q = priorityqueue

كمرجع هنا هو سؤال حول Java PriorityQueue على stackoverflow http://stackoverflow.com/questions/683041/java-how-do-i-use-a-priorityqueue. من المثير للاهتمام أن تتم معالجة الأولوية بواسطة مقارنة بدلاً من مجرد كائن غلاف ذي أولوية int بسيط. على سبيل المثال ، لا يسهل تغيير أولوية عنصر في قائمة الانتظار بالفعل.

مراجعة API:
نحن نتفق على أنه من المفيد وجود هذا النوع في CoreFX ، لأننا نتوقع أن تستخدمه CoreFX.

للمراجعة النهائية لشكل واجهة برمجة التطبيقات ، نود أن نرى نموذجًا للشفرة: PriorityQueue<Thread> و PriorityQueue<MyClass> .

  1. كيف نحافظ على الأولوية؟ في الوقت الحالي يتم تضمينه فقط بواسطة T .
  2. هل نريد أن يكون من الممكن اجتياز الأولوية عند إضافة إدخال؟ (الذي يبدو مفيدًا جدًا)

ملحوظات:

  • نتوقع ألا تتغير الأولوية من تلقاء نفسها - سنحتاج إما إلى واجهة برمجة تطبيقات لها ، أو نتوقع Remove و Add في قائمة الانتظار.
  • نظرًا لأنه ليس لدينا رمز عميل واضح هنا (فقط رغبة عامة نريد النوع) ، فمن الصعب تحديد ما الذي يجب تحسينه - الأداء مقابل قابلية الاستخدام مقابل شيء آخر؟

سيكون هذا نوعًا مفيدًا حقًا في CoreFX. هل أي شخص مهتم في الاستيلاء على هذا؟

لا أحب فكرة إصلاح قائمة انتظار الأولوية إلى كومة ثنائية. قم بإلقاء نظرة على صفحة AlgoKit wiki الخاصة بي للحصول على التفاصيل. أفكار سريعة:

  • إذا كنا نصلح التنفيذ لنوع معين من الكومة ، فاجعلها كومة رباعية.
  • لا ينبغي أن نصلح تنفيذ الكومة ، كما هو الحال في بعض السيناريوهات ، تكون بعض أنواع الأكوام أكثر أداءً من غيرها. وبالتالي ، نظرًا لأننا نصلح التنفيذ إلى نوع معين من الكومة ويحتاج العميل إلى نوع مختلف للحصول على مزيد من الأداء ، فسيحتاج إلى تنفيذ قائمة انتظار الأولوية بالكامل من الألف إلى الياء. ذلك خطأ. يجب أن نكون أكثر مرونة هنا والسماح له بإعادة استخدام بعض كود CoreFX (على الأقل بعض الواجهات).

في العام الماضي قمت بتنفيذ بعض أنواع الكومة . على وجه الخصوص ، هناك واجهة IHeap يمكن استخدامها كقائمة انتظار ذات أولوية.

  • لماذا لا نسميها مجرد كومة ...؟ هل تعرف أي طريقة عقلانية لتنفيذ قائمة انتظار ذات أولوية بخلاف استخدام كومة؟

أنا جميعًا لتقديم واجهة IHeap وبعض التطبيقات الأكثر أداءً (على الأقل تلك القائمة على المصفوفة). واجهة برمجة التطبيقات والتطبيقات موجودة في المستودع الذي ربطته أعلاه.

لذلك لا توجد قوائم انتظار _. أكوام .

ماذا تعتقد؟

karelzsaferndanmosemsft

pgolebiowski لا تنس أننا نصمم واجهة برمجة تطبيقات سهلة الاستخدام وأداء جميل. إذا كنت تريد PriorityQueue (وهو مصطلح علم الكمبيوتر) ، فيجب أن تكون قادرًا على العثور على واحد في المستندات / عبر البحث على الإنترنت.
إذا كان التنفيذ الأساسي عبارة عن كومة (وأنا شخصياً أتساءل لماذا) ، أو أي شيء آخر ، فلا يهم كثيرًا. بافتراض أنه يمكنك إثبات أن التنفيذ أفضل بشكل قابل للقياس من البدائل الأبسط (يعد تعقيد الكود أيضًا مقياسًا مهمًا إلى حد ما).

لذا ، إذا كنت لا تزال تعتقد أن التنفيذ المستند إلى الكومة (بدون IHeap API) أفضل من بعض التنفيذ البسيط المستند إلى القائمة أو القائمة على قائمة المصفوفات ، فيرجى توضيح السبب (من الناحية المثالية في بضع جمل / فقرات) ) ، حتى نتمكن من الحصول على اتفاق بشأن نهج التنفيذ (وتجنب إضاعة الوقت من جانبك في التنفيذ المعقد الذي قد يتم رفضه في وقت مراجعة العلاقات العامة).

إسقاط ICollection ، IEnumerable ؟ فقط لديك الإصدارات العامة (على الرغم من أن IEnumerable<T> سيجلب IEnumerable )

pgolebiowski كيف أن تنفيذها لا يغير واجهة برمجة التطبيقات الخارجية. يحدد PriorityQueue السلوك / العقد؛ في حين أن Heap هو تنفيذ محدد.

قوائم الانتظار ذات الأولوية مقابل الأكوام

إذا كان التنفيذ الأساسي عبارة عن كومة (وأنا شخصياً أتساءل لماذا) ، أو أي شيء آخر ، فلا يهم كثيرًا. بافتراض أنه يمكنك إثبات أن التنفيذ أفضل بشكل قابل للقياس من البدائل الأبسط (يعد تعقيد الكود أيضًا مقياسًا مهمًا إلى حد ما).

لذا ، إذا كنت لا تزال تعتقد أن التنفيذ المستند إلى الكومة أفضل من بعض التنفيذ البسيط المستند إلى قائمة أو قائمة المصفوفات ، فيرجى توضيح السبب (من الناحية المثالية في بضع جمل / فقرات) ، حتى نتمكن من الحصول على اتفاق بشأن نهج التنفيذ [...]

نعم. طابور الأولوية هو بنية بيانات مجردة التي يمكن تنفيذها ثم بطريقة أو بأخرى. بالطبع يمكنك تنفيذه بهيكل بيانات مختلف عن الكومة. لكن لا توجد طريقة أكثر كفاءة. نتيجة ل:

  • غالبًا ما يتم استخدام قائمة انتظار الأولوية وكومة الذاكرة المؤقتة كمرادفات (انظر Python doc أدناه كأوضح مثال على ذلك)
  • عندما يكون لديك دعم "قائمة انتظار ذات أولوية" في بعض المكتبات ، فإنه يستخدم كومة (انظر جميع الأمثلة أدناه)

لدعم كلامي ، لنبدأ بالدعم النظري. مقدمة في الخوارزميات ، كورمين:

[...] تأتي قوائم الانتظار ذات الأولوية في شكلين: قوائم الانتظار ذات الأولوية القصوى وقوائم الانتظار ذات الأولوية الدنيا. سنركز هنا على كيفية تنفيذ قوائم الانتظار ذات الأولوية القصوى ، والتي تعتمد بدورها على الأكوام القصوى.

صرح بوضوح أن قوائم الانتظار ذات الأولوية هي أكوام. هذا اختصار بالطبع ، لكنك حصلت على الفكرة. الآن ، والأهم من ذلك ، دعونا نلقي نظرة على كيفية قيام اللغات الأخرى ومكتباتها القياسية بإضافة دعم للعمليات التي نناقشها:

  • جافا: PriorityQueue<T> - تم تنفيذ قائمة انتظار الأولوية بكومة.
  • الصدأ: BinaryHeap - كومة صريحة في API. تقول في المستندات إنها قائمة انتظار ذات أولوية يتم تنفيذها باستخدام كومة ثنائية. لكن واجهة برمجة التطبيقات واضحة جدًا بشأن الهيكل - كومة.
  • Swift: CFBinaryHeap - مرة أخرى ، يوضح بوضوح ما هي بنية البيانات ، متجنبًا استخدام المصطلح المجرد "قائمة انتظار الأولوية". المستندات التي تصف الفئة: يمكن أن تكون الأكوام الثنائية مفيدة كقوائم انتظار ذات أولوية. أنا أحب النهج.
  • C ++: priority_queue - مرة أخرى ، تم تنفيذه بشكل قانوني باستخدام كومة ثنائية مبنية في الجزء العلوي من المصفوفة.
  • Python: heapq - يتم عرض الكومة بشكل صريح في واجهة برمجة التطبيقات. تم ذكر قائمة انتظار الأولوية فقط في المستندات: توفر هذه الوحدة تنفيذًا لخوارزمية قائمة انتظار كومة الذاكرة المؤقتة ، والمعروفة أيضًا باسم خوارزمية قائمة الانتظار ذات الأولوية.
  • Go: heap package - حتى أن هناك واجهة كومة. لا توجد قائمة انتظار صريحة ذات أولوية ، ولكن مرة أخرى فقط في المستندات: الكومة هي طريقة شائعة لتنفيذ قائمة انتظار الأولوية.

أعتقد بشدة أنه يجب علينا السير في طريق Rust / Swift / Python / Go وفضح كومة بشكل صريح مع الإشارة بوضوح في المستندات إلى أنه يمكن استخدامها كقائمة انتظار ذات أولوية. أعتقد اعتقادا راسخا أن هذا النهج نظيف وبسيط للغاية.

  • نحن واضحون بشأن هيكل البيانات ونترك واجهة برمجة التطبيقات يتم تحسينها في المستقبل - إذا توصل شخص ما بطريقة ثورية جديدة لتنفيذ قائمة انتظار ذات أولوية أفضل في بعض الجوانب (وسيعتمد خيار كومة الخيارات مقابل النوع الجديد على السيناريو) ، يمكن أن تظل واجهة برمجة التطبيقات الخاصة بنا سليمة. يمكننا ببساطة إضافة النوع الثوري الجديد الذي يعزز المكتبة - وستظل فئة الكومة موجودة هناك.
  • تخيل أن مستخدمًا يتساءل عما إذا كانت بنية البيانات التي نختارها مستقرة. عندما يكون الحل الذي نتبعه هو قائمة انتظار ذات أولوية ، فهذا ليس واضحًا. المصطلح مجرّد ويمكن أن يكون أي شيء تحته. وبالتالي يضيع المستخدم بعض الوقت للبحث في المستندات ومعرفة أنه يستخدم كومة داخليًا وبالتالي فهو غير مستقر. كان من الممكن تجنب هذا فقط بالقول صراحةً من خلال واجهة برمجة التطبيقات (API) أن هذا كومة.

آمل أن تعجبكم جميعًا طريقة الكشف بوضوح عن بنية بيانات كومة - وإنشاء مرجع لقائمة انتظار ذات أولوية في المستندات فقط.

API والتنفيذ

نحتاج إلى الاتفاق على الأمر أعلاه ، لكن اسمحوا لي أن أبدأ موضوعًا آخر - كيفية تنفيذ ذلك . يمكنني رؤية حلين هنا:

  • نحن نقدم فقط فئة ArrayHeap . عند رؤية الاسم ، يمكنك على الفور معرفة نوع الكومة التي تتعامل معها (مرة أخرى ، هناك العشرات من أنواع الكومة). ترى أنه يعتمد على مجموعة. أنت تعرف على الفور الوحش الذي تتعامل معه.
  • الاحتمال الآخر الذي يعجبني أكثر هو توفير واجهة IHeap وتقديم تطبيق واحد أو أكثر. يمكن للعميل كتابة التعليمات البرمجية التي تعتمد على الواجهات - وهذا من شأنه أن يسمح بتوفير تطبيقات واضحة وقابلة للقراءة حقًا للخوارزميات المعقدة. تخيل كتابة فئة DijkstraAlgorithm . يمكن أن يعتمد ببساطة على واجهة IHeap (مُنشئ معلمات واحد) أو مجرد استخدام ArrayHeap (المُنشئ الافتراضي). نظيف ، بسيط ، واضح ، لا لبس فيه بسبب استخدام مصطلح "قائمة انتظار الأولوية". وواجهة رائعة منطقية جدًا من الناحية النظرية.

في الطريقة أعلاه ، يمثل ArrayHeap شجرة d-ary كاملة مرتبة بأعداد كبيرة ، مخزنة كمصفوفة. يمكن استخدام هذا لإنشاء على سبيل المثال BinaryHeap أو QuaternaryHeap .

الحد الأدنى

لمزيد من المناقشة ، أشجعك بشدة على إلقاء نظرة على هذه الورقة . ستعرف أن:

  • 4-ary heaps (رباعي) هي ببساطة أسرع من 2-ary heaps (ثنائي). هناك العديد من الاختبارات التي تم إجراؤها في الورقة - قد تكون مهتمًا بمقارنة أداء implicit_4 و implicit_2 (أحيانًا implicit_simple_4 و implicit_simple_2 ).

  • يعتمد الاختيار الأمثل للتنفيذ بشدة على المدخلات. من بين أكوام d-ary الضمنية ، وأكوام الاقتران ، وأكوام فيبوناتشي ، وأكوام ذات الحدين ، وأكوام d-ary الصريحة ، وأكوام اقتران الرتب ، وأكوام الزلازل ، وأكوام الانتهاك ، وأكوام الرتب الضعيفة ، وأكوام فيبوناتشي الصارمة ، يبدو أن الأنواع التالية تغطية جميع الاحتياجات تقريبًا لمختلف السيناريوهات:

    • أكوام d-ary الضمنية (ArrayHeap) ، <- بالتأكيد نحن بحاجة إلى هذا
    • اقتران أكوام ، <- إذا اتبعنا الأسلوب الجميل والنظيف IHeap ، فسيكون من المفيد إضافة هذا ، لأنه في كثير من الحالات يكون سريعًا بالفعل وأسرع من الحل القائم على المصفوفة .

    بالنسبة لكليهما ، فإن جهد البرمجة منخفض بشكل مدهش. ألق نظرة على تطبيقاتي.


تضمين التغريدة

سيكون هذا نوعًا مفيدًا حقًا في CoreFX. هل أي شخص مهتم في الاستيلاء على هذا؟

safern سأكون سعيدًا جدًا للحصول على هذا من الآن فصاعدًا.

حسنًا ، لقد كانت مشكلة بين لوحة المفاتيح وكرسي - PriorityQueue يعتمد بالطبع على Heap - كنت أفكر فقط Queue حيث لا يكون له معنى ونسيت أن الكومة "مرتبة" - عملية تفكير محرجة للغاية بالنسبة لشخص مثلي ، يحب المنطق ، والخوارزميات ، وآلات تورينج ، وما إلى ذلك ، اعتذاري. (راجع للشغل: بمجرد أن قرأت جملتين في رابط مستندات جافا الخاص بك ، تم النقر على التناقض على الفور)

من هذا المنظور ، من المنطقي إنشاء واجهة برمجة التطبيقات أعلى Heap . يجب ألا نجعل هذا الفصل عامًا حتى الآن - سيتطلب ذلك مراجعة API الخاصة به ومناقشته الخاصة إذا كان ذلك شيئًا نحتاجه في CoreFX. لا نريد زحف سطح API بسبب التنفيذ ، ولكن قد يكون الشيء الصحيح الذي يجب القيام به - ومن ثم يلزم المناقشة.
من هذا المنظور ، لا أعتقد أننا بحاجة إلى إنشاء IHeap الآن. قد يكون قرارا جيدا في وقت لاحق.
إذا كان هناك بحث يفيد بأن الكومة المحددة (على سبيل المثال 4-ary كما ذكرت أعلاه) هي الأفضل للإدخال العشوائي العام ، فيجب علينا اختيار ذلك. الانتظار دعونا لianhayssafernstephentoub للرأي تأكيد / صوت.

إن تحديد كومة أساسية مع خيارات متعددة مطبقة هو شيء لا تنتمي إليه IMO في CoreFX (قد أكون مخطئًا هنا ، مرة أخرى - دعنا نرى ما يعتقده الآخرون).
السبب وراء ذلك هو أننا سنقوم قريبًا بشحن مجموعات من المجموعات المتخصصة التي سيكون من الصعب جدًا على الأشخاص (مطور متوسط ​​بدون خلفية قوية في الفروق الدقيقة في الخوارزميات) الاختيار من بينها. ومع ذلك ، فإن مثل هذه المكتبة من شأنها أن تقدم حزمة NuGet رائعة للخبراء في هذا المجال - التي تمتلكها أنت / المجتمع. في المستقبل ، قد نفكر في إضافتها إلى PowerCollections (نحن نناقش بنشاط خلال الأشهر الأربعة الماضية مكان وضع هذه المكتبة على GitHub وما إذا كان ينبغي لنا امتلاكها ، أو إذا كان علينا تشجيع المجتمع على امتلاكها - هناك آراء مختلفة حول هذا الموضوع ، أتوقع أن ننتهي من مصيرها بعد 2.0)

يعين لك ما تريد العمل عليه ...

تم إرسال دعوة متعاون

benaadams سأحتفظ بـ ICollection (تفضيل معتدل). من أجل الاتساق مع ds الأخرى في CoreFX. لا يستحق IMO أن يكون لديك وحش غريب هنا ... إذا كنا نضيف حفنة جديدة (مثل PowerCollections حتى إلى repo آخر) ، لا ينبغي لنا تضمين غير العامة ... الأفكار؟

حسنًا ، كانت مشكلة بين لوحة المفاتيح وكرسي.

هاها 😄 لا تقلق.

من المنطقي إنشاء واجهة برمجة التطبيقات أعلى الكومة. لا ينبغي أن نجعل هذه الفئة عامة حتى الآن [...] لا نريد زحف سطح واجهة برمجة التطبيقات بسبب التنفيذ ، ولكن قد يكون الشيء الصحيح الذي يجب القيام به - ومن ثم يلزم المناقشة. [...] لا أعتقد أننا بحاجة إلى إنشاء IHeap حتى الآن. قد يكون قرارا جيدا في وقت لاحق.

إذا كان قرار المجموعة هو استخدام PriorityQueue ، فسأساعد فقط في التصميم وتنفيذ ذلك. ومع ذلك ، يرجى مراعاة حقيقة أنه إذا أضفنا PriorityQueue الآن ، فسيكون من الفوضى في واجهة برمجة التطبيقات لاحقًا إضافة Heap - لأن كلاهما يتصرفان بنفس الطريقة. سيكون نوعا من التكرار IMO. ستكون هذه رائحة تصميم بالنسبة لي. لن أضيف قائمة انتظار الأولوية. لا يساعد.

أيضا ، فكر واحد آخر. في الواقع ، يمكن أن يكون هيكل بيانات كومة الاقتران مفيدًا إلى حد ما في كثير من الأحيان. الأكوام المستندة إلى المصفوفة مروعة في دمجها. هذه العملية هي في الأساس خطية . عندما يكون لديك الكثير من أكوام الدمج ، فإنك تقتل الأداء. ومع ذلك ، إذا كنت تستخدم كومة اقتران بدلاً من كومة صفيف - تكون عملية الدمج ثابتة (مستهلكة). هذه حجة أخرى لماذا أرغب في تقديم واجهة لطيفة وتطبيقيْن. واحد للمدخلات العامة ، والثاني لبعض السيناريوهات المحددة ، خاصة عند دمج أكوام.

التصويت لـ IHeap + ArrayHeap + PairingHeap ! 😄 (مثل Rust / Swift / Python / Go)

إذا كانت كومة الاقتران كبيرة جدًا - حسنًا. لكن دعنا على الأقل نذهب بـ IHeap + ArrayHeap . ألا تشعرون يا رفاق أن الذهاب مع فئة PriorityQueue يقفل الاحتمالات في المستقبل ويجعل واجهة برمجة التطبيقات أقل وضوحًا؟

ولكن كما قلت - إذا صوتتم جميعًا لفئة PriorityQueue على الحل المقترح - حسنًا.

تم إرسال دعوة المتعاون - عندما تقبل اختبار الاتصال بي ، سأكون قادرًا على تعيينها لك بعد ذلك (قيود GitHub).

@ karelz بينغ :)

يرجى الأخذ في الاعتبار حقيقة أنه إذا أضفنا PriorityQueue الآن ، فسيكون من الفوضى في API لاحقًا لإضافة Heap - حيث يتصرف كلاهما بشكل أساسي كما هو. سيكون نوعا من التكرار IMO. ستكون هذه رائحة تصميم بالنسبة لي. لن أضيف قائمة انتظار الأولوية. لا يساعد.

هل يمكنك شرح المزيد من التفاصيل حول سبب الفوضى في وقت لاحق؟ ما هي اهتماماتك؟
PriorityQueue هو المفهوم الذي يستخدمه الناس. وجود نوع يسمى بهذه الطريقة مفيد ، أليس كذلك؟
أعتقد أن العمليات المنطقية (على الأقل أسمائهم) على Heap قد تكون مختلفة. إذا كانت متطابقة ، فيمكن أن يكون لدينا تطبيقان مختلفان لنفس الشفرة في أسوأ الحالات (ليست مثالية ، ولكن ليس نهاية العالم). أو يمكننا إدخال فئة Heap كوالد PriorityQueue ، أليس كذلك؟ (على افتراض أنه مسموح به من منظور مراجعة واجهة برمجة التطبيقات - في الوقت الحالي لا أرى سببًا لعدم ذلك ، لكن ليس لدي سنوات عديدة من الخبرة في مراجعات واجهة برمجة التطبيقات ، لذلك سأنتظر حتى يؤكد الآخرون)

دعونا نرى كيف يذهب التصويت والمزيد من مناقشة التصميم ... أنا أعمل ببطء على الاستعداد لفكرة IHeap + ArrayHeap ، لكنني لست مقتنعًا تمامًا بعد ...

إذا كنا نضيف حفنة جديدة ... لا ينبغي لنا تضمين غير العامة

قطعة قماش حمراء للثور. أي شخص لديه بعض المجموعات الأخرى لإضافتها حتى نتمكن من إسقاط ICollection ؟

دائري / حلقة عازلة ؛ عام ومتزامن؟

karelz قد يكون حل مشكلة التسمية شيئًا مثل IPriorityQueue مثل DataFlow لأنماط الإنتاج / المستهلك. العديد من الطرق لتنفيذ قائمة انتظار ذات أولوية ، وإذا كنت لا تهتم بذلك ، فاستخدم الواجهة. اهتم بالتنفيذ أو تقوم بإنشاء مثيل يستخدم فئة التنفيذ.

هل يمكنك شرح المزيد من التفاصيل حول سبب الفوضى في وقت لاحق؟ ما هي اهتماماتك؟
PriorityQueue هو المفهوم الذي يستخدمه الناس. وجود نوع يسمى بهذه الطريقة مفيد ، أليس كذلك؟ […] أعمل ببطء على الاستعداد لفكرة IHeap + ArrayHeap ، لكني لست مقتنعًا تمامًا بعد ...

karelz من تجربتي ، أجد أنه من المهم حقًا الحصول على فكرة مجردة ( IPriorityQueue أو IHeap ). بفضل هذا النهج ، يمكن للمطور كتابة رمز منفصل. نظرًا لأنه مكتوب مقابل واجهة (بدلاً من تطبيق محدد) ، فهناك المزيد من المرونة وروح IoC. من السهل جدًا كتابة اختبارات الوحدة لمثل هذا الرمز (باستخدام حقن التبعية ، يمكن للمرء حقن IPriorityQueue أو IHeap وسخرية منه ومعرفة ما هي الأساليب التي يتم استدعاؤها في أي وقت وبأي وسيطات). التجريد جيد.

صحيح أن مصطلح "قائمة انتظار الأولوية" يستخدم بشكل شائع. تكمن المشكلة في أن هناك طريقة واحدة فقط لتنفيذ قائمة انتظار ذات أولوية بشكل فعال - باستخدام كومة. الكثير من أنواع الأكوام. لذلك يمكننا الحصول على:

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

أو

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

بالنسبة لي ، يبدو النهج الثاني أفضل. كيف تشعر حيال ذلك؟

فيما يتعلق بفئة PriorityQueue - أعتقد أنها ستكون غامضة للغاية وأنا ضد مثل هذه الفئة تمامًا. الواجهة الغامضة جيدة ، لكنها ليست تطبيقًا. إذا كانت كومة ثنائية ، فقط أطلق عليها BinaryHeap . إذا كان شيئًا آخر ، فسمه وفقًا لذلك.

أنا أؤيد جميعًا تسمية الفئات بشكل واضح بسبب مشاكل الفصول مثل SortedDictionary . هناك الكثير من الالتباس بين المطورين حول ما يمثله وكيف يختلف عن SortedList . إذا كان يطلق عليه للتو BinarySearchTree ، فستكون الحياة أبسط ويمكننا العودة إلى المنزل ورؤية أطفالنا في وقت مبكر.

دعونا نسمي كومة كومة.

benaadams يجب على كل منهم تحويله إلى CoreFX (أي يجب أن يكون ذا قيمة لكود CoreFX نفسه ، أو يجب أن يكون أساسيًا

النتيجة الأكثر ترجيحًا (الادعاء المتحيز ، لأنني أحب الحل) هي أننا سننشئ ريبوًا آخر ونقوم بتجهيزه باستخدام PowerCollections ثم نسمح للمجتمع بتوسيعه ببعض التوجيه / الإشراف الأساسي من فريقنا.
راجع للشغل: تعتقد terrajobst أن الأمر لا يستحق ذلك ("لدينا أشياء أفضل وأكثر تأثيرًا في النظام البيئي يجب القيام بها") ويجب أن نشجع المجتمع على
// أعتقد أن هناك فرصة للمجتمع للقفز على هذا الحل قبل أن نفكر مليًا ؛-). سيؤدي ذلك إلى جعل المناقشة (وتفضيلي) صامتة ؛-)

pgolebiowski ، أنت Heap أفضل من PriorityQueue - سنحتاج فقط إلى توجيه قوي ومستندات "هذه هي الطريقة التي تعمل بها PriorityQueue - استخدم Heap "... هذا يمكن أن يعمل.

ومع ذلك ، أنا متردد للغاية في تضمين أكثر من تطبيق كومة في CoreFX. 98٪ + من مطوري C # "العاديين" لا يهتمون. إنهم لا يريدون حتى التفكير في أيهما هو الأفضل ، بل يحتاجون فقط إلى شيء ينجز المهمة. لم يتم تصميم كل قطعة من كل SW مع وضع الأداء العالي في الاعتبار ، بشكل صحيح. فكر في جميع الأدوات التي يتم استخدامها مرة واحدة ، وتطبيقات واجهة المستخدم ، وما إلى ذلك. ما لم تكن تصمم نظامًا لتوسيع النطاق عالي الأداء حيث يكون هذا النظام على المسار الحرج ، فلا يجب أن تهتم.

بالطريقة نفسها ، لا يهمني كيف يتم تنفيذ SortedDictionary أو ArrayList أو سواها - يقومون بعملهم بشكل لائق. أفهم (مثل كثيرين آخرين) أنه إذا كنت بحاجة إلى أداء عالٍ من هذه السيناريوهات لسيناريوهاتي ، فأنا بحاجة إلى قياس الأداء و / أو التحقق من التنفيذ ويجب أن أقرر ما إذا كان جيدًا بما يكفي لسيناريوهاتي ، أو إذا كنت بحاجة إلى ذلك طرح التنفيذ الخاص بي للحصول على أفضل أداء ، ومناسب لاحتياجاتي.

يجب علينا تحسين قابلية الاستخدام لـ 98٪ من حالات الاستخدام ، وليس لـ 2٪. إذا طرحنا عددًا كبيرًا جدًا من الخيارات (عمليات التنفيذ) وأجبرنا الجميع على اتخاذ القرار ، فقد تسببنا في حدوث ارتباك غير ضروري لـ 98٪ من حالات الاستخدام. لا أعتقد أن الأمر يستحق ذلك ...
يتمتع نظام IMO .NET البيئي بقيمة كبيرة في تقديم اختيار أحادي للعديد من واجهات برمجة التطبيقات (وليس فقط المجموعات) مع خصائص أداء جيدة للغاية ومفيدة لمعظم حالات الاستخدام. ونقدم نظامًا بيئيًا يسمح بامتدادات عالية الأداء لأولئك الذين يحتاجون إليها والذين يرغبون في التعمق أكثر ومعرفة المزيد / اتخاذ خيارات ومفاضلات مدروسة.

ومع ذلك ، فإن استخدام واجهة IHeap (مثل IDictionary و IReadOnlyDictionary ) ، قد يكون منطقيًا - يجب أن أفكر في الأمر أكثر قليلاً / اسأل خبراء مراجعة API في فضاء ...

لدينا بالفعل (إلى حد ما) ما يتحدث pgolebiowski مع ISet<T> و HashSet<T> . أقول فقط عكسها. لذلك تم تغيير واجهة برمجة التطبيقات أعلاه إلى واجهة ( IPriorityQueue<T> ) ومن ثم لدينا تطبيق ( HeapPriorityQueue<T> ) يستخدم داخليًا كومة قد تكون أو لا يتم عرضها علنًا لأنها فئة خاصة بها.

هل يجب ( PriorityQueue<T> ) أيضًا تنفيذ IList<T> ؟

karelz مشكلتي مع ICollection هي SyncRoot و IsSynchronized ؛ إما أنها تنفذ مما يعني أن هناك تخصيصًا إضافيًا لكائن القفل ؛ أو يرمون ، عندما يكون من غير المجدي امتلاكهم.

benaadams سيكون هذا مضللًا. نظرًا لأن 99.99٪ من عمليات تنفيذ قوائم الانتظار ذات الأولوية عبارة عن أكوام تعتمد على المصفوفات (وكما أرى ، فإننا نذهب مع أحدها هنا أيضًا) ، فهل يعني ذلك تعريض الوصول إلى البنية الداخلية للمصفوفة؟

لنفترض أن لدينا كومة تحتوي على العناصر 4 ، 8 ، 10 ، 13 ، 30 ، 45. بالنظر إلى الترتيب ، يمكن الوصول إليها من خلال الفهارس 0 ، 1 ، 2 ، 3 ، 4 ، 5. ومع ذلك ، فإن البنية الداخلية للكومة هو [4 ، 8 ، 30 ، 10 ، 13 ، 45] (في النظام الثنائي ، في النظام الرباعي سيكون مختلفًا).

  • لا تبدو إعادة الرقم الداخلي في الفهرس i منطقية من وجهة نظر المستخدم ، لأنها تقريبًا عشوائية.
  • إرجاع رقم بالترتيب (حسب الأولوية) مكلف للغاية - هذا خطي.
  • إعادة أيٍّ من هذين الأمرين غير منطقي حقًا في تطبيقات أخرى. غالبًا ما يكون الأمر مجرد ظهور عناصر i ، والحصول على العنصر i -th ، ثم دفعها مرة أخرى.

عادةً ما يكون IList<T> هو الحل الوقح من أجل: أريد أن أكون مرنًا مع المجموعات التي تقبلها واجهة برمجة التطبيقات الخاصة بي ، وأريد تعدادها ولكن لا أريد تخصيصها عبر IEnumerable<T>

أدركت للتو أنه لا توجد واجهة عامة لـ

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

لذا لا تهتم بهذا 😢 (على الرغم من أنها من نوع IReadOnlyCollection)

ولكن يجب تنفيذ إعادة التعيين على Enumerator بشكل صريح لأنه سيئ ويجب أن يتم إلقاؤه فقط.

لذلك اقترحت التغييرات

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

نظرًا لأنك تناقش الأساليب ... لم نتفق بعد على IHeap vs IPriorityQueue thingy - إنه يؤثر على أسماء الأساليب والمنطق قليلاً. ومع ذلك ، في كلتا الحالتين ، أجد أن اقتراح واجهة برمجة التطبيقات الحالي يفتقد ما يلي:

  • القدرة على إزالة عنصر معين من قائمة الانتظار
  • القدرة على تحديث أولوية العنصر
  • القدرة على دمج هذه الهياكل

هذه العمليات بالغة الأهمية ، لا سيما إمكانية تحديث عنصر. بدون هذا ، لا يمكن تنفيذ العديد من الخوارزميات. نحن بحاجة إلى إدخال نوع المقبض. في واجهة برمجة التطبيقات هنا ، المقبض هو IHeapNode . وهي حجة أخرى لاتباع طريقة IHeap لأنه بخلاف ذلك سيتعين علينا تقديم PriorityQueueHandle النوع الذي سيكون دائمًا مجرد عقدة كومة ... 😜 بالإضافة إلى أنه مجرد معنى غامض ... بينما ، عقدة الكومة - يعرف الجميع الأشياء ويمكنهم تخيل الشيء الذي يتعاملون معه.

في الواقع ، بعد قليل ، بالنسبة لاقتراح API ، يرجى إلقاء نظرة على هذا الدليل . ربما نحتاج فقط إلى مجموعة فرعية منه. ولكن مع ذلك - فهو يحتوي فقط على ما نحتاجه IMO ، لذلك قد يكون من المفيد إلقاء نظرة عليه كنقطة بداية.

ما هي افكاركم يا رفاق؟

لا يختلف IHeapNode كثيرًا عن نوع clr KeyValuePair ؟

ومع ذلك ، فهذا يفصل بعد ذلك الأولوية ويكتب حتى الآن PriorityQueue<TKey, TValue> مع IComparer<TKey> comparer ؟

KeyValuePair ليس فقط بنية ولكن خصائصه للقراءة فقط. سيعادل بشكل أساسي إنشاء كائن جديد في كل مرة يتم فيها تحديث الهيكل.

استخدام مفتاح فقط لا يعمل مع مفاتيح متساوية - هناك حاجة إلى مزيد من المعلومات لمعرفة العنصر المراد تحديثه / إزالته.

من IHeapNode و 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;
        }
    }

وطريقة التحديث داخل 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);
        }

ولكن الآن كل عنصر في الكومة هو كائن إضافي ؛ ماذا لو أردت سلوك PriorityQueue لكنني لا أريد هذه التخصيصات الإضافية؟

ثم لن تكون قادرًا على تحديث / إزالة العناصر. سيكون تطبيقًا مكشوفًا لا يسمح لك بتنفيذ أي خوارزمية تعتمد على عملية DecreaseKey (وهو أمر شائع بشكل كبير). على سبيل المثال خوارزمية Dijkstra ، خوارزمية Prim. أو إذا كنت تكتب نوعًا من برنامج الجدولة وقد غيرت بعض العمليات (أو أيًا كان) أولويتها ، فلا يمكنك معالجة ذلك.

أيضًا ، في كل تطبيق كومة أخرى - العناصر هي مجرد عقد ، بشكل صريح. إنه طبيعي تمامًا في حالات أخرى ، هنا يكون مصطنعًا بعض الشيء ، ولكنه ضروري للتحديث / الإزالة.

في Java ، لا تحتوي قائمة انتظار الأولوية على خيار تحديث العناصر. نتيجة:

عندما قمت بتنفيذ أكوام من أجل مشروع AlgoKit وواجهت كل مشاكل التصميم هذه ، اعتقدت أن هذا هو السبب في أن مؤلفي .NET قرروا عدم إضافة بنية بيانات أساسية مثل كومة (أو قائمة انتظار ذات أولوية). لأن كلا التصميمين ليس جيدًا. لكل منها عيوبها.

باختصار - إذا أردنا إضافة بنية بيانات تدعم الترتيب الفعال للعناصر حسب أولويتها ، فمن الأفضل القيام بذلك بالطريقة الصحيحة وإضافة وظائف مثل تحديث / إزالة العناصر منه.

إذا كنت تشعر بالسوء حيال التفاف العناصر في مصفوفة بكائن آخر - فهذه ليست المشكلة الوحيدة. آخر يندمج. مع الأكوام المستندة إلى المصفوفة ، هذا غير فعال تمامًا. ومع ذلك ... إذا استخدمنا بنية بيانات كومة الاقتران ( كومة الاقتران: شكل جديد من كومة الضبط الذاتي ) ، إذن:

  • مقابض العناصر هي عقد رائعة - يجب تخصيصها على أي حال ، لذلك لا توجد أشياء فوضوية
  • الدمج ثابت (مقابل الخطي في الحل القائم على المصفوفة)

في الواقع ، يمكننا حل هذا بالطريقة التالية:

  • أضف واجهة IHeap تدعم كل الطرق
  • أضف ArrayHeap و PairingHeap مع كل هذه المقابض ، التحديث ، الإزالة ، الدمج
  • أضف PriorityQueue وهو مجرد غلاف حول ArrayHeap ، مما يبسط واجهة برمجة التطبيقات

PriorityQueue في System.Collections.Generic وجميع الأكوام في System.Collections.Specialized .

يعمل؟

من غير المحتمل إلى حد ما أن نحصل على ثلاثة هياكل بيانات جديدة من خلال مراجعة واجهة برمجة التطبيقات. في معظم الحالات ، يكون استخدام كمية أقل من API هو الأفضل. يمكننا دائمًا إضافة المزيد لاحقًا إذا انتهى الأمر بعدم كفاية ، لكن لا يمكننا إزالة API.

هذا أحد الأسباب التي تجعلني لست من محبي فئة HeapNode. يجب أن يكون هذا النوع من الأشياء داخليًا فقط ويجب أن تعرض واجهة برمجة التطبيقات نوعًا موجودًا بالفعل إذا كان ذلك ممكنًا - في هذه الحالة ربما يكون KVP.

ianhays إذا تم الاحتفاظ بهذا الأمر داخليًا فقط ، فلن يتمكن المستخدمون من تحديث أولويات العناصر في بنية البيانات. سيكون هذا عديم الفائدة إلى حد كبير وسينتهي بنا الأمر مع جميع مشكلات Java - يقوم الأشخاص بإعادة تنفيذ بنية البيانات الموجودة بالفعل في المكتبة الأصلية ... يبدو ذلك سيئًا بالنسبة لي. أسوأ بكثير من وجود فئة بسيطة تمثل العقدة.

راجع للشغل: تحتوي القائمة المرتبطة على فئة عقدة بحيث يمكن للمستخدمين استخدام الوظيفة المناسبة. هذا انعكاس إلى حد كبير.

لن يتمكن المستخدمون من تحديث أولويات العناصر في هيكل البيانات.

هذا ليس صحيحًا بالضرورة. يمكن الكشف عن الأولوية بطريقة لا تتطلب بنية بيانات إضافية مثل تلك بدلاً من

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

        }

سيكون لديك على سبيل المثال

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

        }

لست مقتنعًا حتى الآن بكشف الأولوية كمعامل نوع أيضًا. Imo سيستخدم غالبية الأشخاص نوع الأولوية الافتراضي وستكون قيمة TV قيمة غير ضرورية.

void Update(TKey item, TValue priority)

أولاً - ما هو TKey و TValue في شفرتك؟ كيف من المفترض أن تعمل؟ الاتفاقية في علوم الكمبيوتر هي:

  • مفتاح = الأولوية
  • القيمة = كل ما تريد تخزينه في عنصر

ثانيا:

Imo سيستخدم غالبية الأشخاص نوع الأولوية الافتراضي وستكون قيمة TV قيمة غير ضرورية.

يرجى تحديد "نوع الأولوية الافتراضي". أشعر أنك تريد فقط الحصول على PriorityQueue<T> ، هل هذا صحيح؟ إذا كانت هذه هي الحالة ، ففكر في حقيقة أنه من المحتمل أن يقوم المستخدم بإنشاء فئة جديدة ، أو غلاف حول أولويته وقيمته ، بالإضافة إلى تنفيذ شيء مثل IComparable أو تقديم مقارنة مخصصة. فقراء جدا.

أولاً - ما هو TKey و TValue في الكود الخاص بك؟

العنصر وأولوية العنصر. يمكنك تبديلها لتكون الأولوية والعنصر المرتبط بهذه الأولوية ، ولكن بعد ذلك يكون لديك مجموعة لا تكون فيها مفاتيح TKeys بالضرورة فريدة (أي السماح بتكرار الأولويات). أنا لست ضد ذلك ، ولكن بالنسبة لي TKey عادة ما يعني التفرد.

النقطة المهمة هي أن عرض فئة العقدة ليس شرطًا لفضح طريقة التحديث.

يرجى تحديد "نوع الأولوية الافتراضي". أشعر أنك تريد فقط الحصول على PriorityQueue، هل هذا صحيح؟

نعم فعلا. على الرغم من قراءة هذا الموضوع مرة أخرى ، فأنا لست مقتنعًا تمامًا بأن هذا هو الحال.

إذا كانت هذه هي الحالة ، ففكر في حقيقة أنه من المحتمل أن يقوم المستخدم بإنشاء فئة جديدة ، أو غلاف حول أولويته وقيمته ، بالإضافة إلى تنفيذ شيء مثل IComparable أو توفير مقارنة مخصصة. فقراء جدا.

أنت لست مخطأ. أتصور أن قدرا كبيرا من المستخدمين لإنشاء نوع المجمع مع منطق المقارنة المخصصة. أتخيل أن هناك أيضًا عددًا لا بأس به من المستخدمين الذين لديهم بالفعل نوع مشابه يريدون وضعه في قائمة الانتظار ذات الأولوية أو نوع بمقارن محدد. السؤال هو أي معسكر هو أكبر من الاثنين.

أعتقد أنه يجب تسمية النوع PriorityQueue<T> ، وليس Heap<T> ، ArrayHeap<T> أو حتى QuaternaryHeap<T> للبقاء متسقًا مع بقية .Net:

  • لدينا List<T> ، وليس ArrayList<T> .
  • لدينا Dictionary<K, V> ، وليس HashTable<K, V> .
  • لدينا ImmutableList<T> ، وليس ImmutableAVLTreeList<T> .
  • لدينا Array.Sort() ، وليس Array.IntroSort() .

عادة لا يهتم المستخدمون من هذه الأنواع بكيفية تنفيذها ، وبالتأكيد لا ينبغي أن يكون هذا هو الشيء الأبرز في هذا النوع.

إذا كانت هذه هي الحالة ، ففكر في حقيقة أنه من المحتمل أن يقوم المستخدم بإنشاء فئة جديدة ، أو غلاف حول أولويته وقيمته ، بالإضافة إلى تنفيذ شيء مثل IComparable أو توفير مقارنة مخصصة.

أنت لست مخطأ. أتخيل أن عددًا لا بأس به من المستخدمين سيضطرون إلى إنشاء نوع غلاف باستخدام منطق مقارن مخصص.

في ملخص واجهة برمجة التطبيقات ، يتم توفير المقارنة بواسطة المقارنة المقدمة IComparer<T> comparer ، ولا يلزم وجود غلاف. غالبًا ما تكون الأولوية جزءًا من النوع ، على سبيل المثال ، سيكون لجدول الوقت وقت التنفيذ كخاصية من النوع.

استخدام KeyValuePair مع IComparer مخصص لا يضيف أي تخصيصات إضافية ؛ على الرغم من أنه يضيف أمرًا غير مباشر إضافيًا للتحديث حيث يلزم مقارنة القيم بدلاً من العنصر.

قد تكون التحديثات مشكلة بالنسبة لعناصر البنية ما لم يتم الحصول على العنصر من خلال إرجاع المرجع ثم تحديثه وإعادته إلى طريقة التحديث بواسطة المرجع وتمت مقارنة المراجع. لكن هذا مروع بعض الشيء.

svick

أعتقد أنه يجب تسمية النوع PriorityQueue<T> ، وليس Heap<T> ، ArrayHeap<T> أو حتى QuaternaryHeap<T> للبقاء متسقًا مع بقية .Net.

أنا أفقد إيماني بالإنسانية.

  • استدعاء بنية البيانات PriorityQueue يتوافق مع بقية .NET وتسميتها Heap ليس كذلك؟ شيء خاطئ مع أكوام؟ يجب أن ينطبق الشيء نفسه على التكديس وقوائم الانتظار بعد ذلك.
  • ArrayHeap -> الفئة الأساسية لـ QuaternaryHeap . فرق كبير.
  • المناقشة ليست مسألة اختيار اسم رائع. هناك الكثير من الأشياء التي تأتي نتيجة اتباع مسار تصميم معين. أعد قراءة الموضوع من فضلك.
  • Hashtable ، ArrayList . يبدو أنهم موجودون. راجع للشغل ، "القائمة" هي اختيار سيء جدًا لتسمية IMO ، حيث أن الفكرة الأولى للمستخدم هي أن List هي قائمة ، لكنها ليست قائمة. 😜
  • لا يتعلق الأمر بالمتعة وقول كيف يتم تنفيذ شيء ما. يتعلق الأمر بإعطاء أسماء ذات معنى تُظهر للمستخدمين على الفور ما يتعاملون معه.

هل تريد البقاء متسقًا مع بقية .NET وتواجه مشكلات مثل هذه؟

وماذا أراه هناك ... يقول جون سكيت أن SortedDictionary يجب أن يُطلق عليه SortedTree لأن هذا يعكس التنفيذ بشكل أوثق.

تضمين التغريدة

شيء خاطئ مع أكوام؟

نعم ، يصف التطبيق وليس الاستخدام.

يجب أن ينطبق الشيء نفسه على التكديس وقوائم الانتظار بعد ذلك.

لا يصف أي منهما التنفيذ حقًا ، على سبيل المثال لا يخبرك الاسم أنهما يعتمدان على المصفوفة.

ArrayHeap -> الفئة الأساسية لـ QuaternaryHeap . فرق كبير.

نعم ، أفهم تصميمك. ما أقوله هو أنه لا يجب الكشف عن أي من هذا للمستخدم.

هناك الكثير من الأشياء التي تأتي نتيجة اتباع مسار تصميم معين. أعد قراءة الموضوع من فضلك.

لقد قرأت الموضوع. لا أعتقد أن هذا التصميم ينتمي إلى BCL. أعتقد أنه يجب أن يحتوي على شيء سهل الاستخدام والفهم ، وليس شيئًا يؤدي إلى تساؤل الناس عن ماهية "الكومة الرباعية" أو ما إذا كان ينبغي عليهم استخدامها.

إذا لم يكن التنفيذ الافتراضي جيدًا بالنسبة لهم ، فهذا هو المكان الذي تأتي فيه المكتبات الأخرى (مثل الخاصة بك).

Hashtable ، ArrayList. يبدو أنهم موجودون.

نعم ، هذه هي فئات .NET Framework 1.0 التي لم يعد يستخدمها أحد. بقدر ما أستطيع أن أقول ، تم نسخ أسمائهم من Java وقرر مصممو .NET Framework 2.0 عدم اتباع هذا الاصطلاح. في رأيي ، كان هذا هو القرار الصحيح.

راجع للشغل ، "القائمة" هي اختيار سيء جدًا لتسمية IMO ، حيث أن الفكرة الأولى للمستخدم هي أن List هي قائمة ، لكنها ليست قائمة.

إنها. إنها ليست قائمة مرتبطة ، لكن هذا ليس نفس الشيء. وأحب ألا أضطر إلى كتابة ArrayList أو ResizeArray (اسم F # لـ List<T> ) في كل مكان.

يتعلق الأمر بإعطاء أسماء ذات معنى تُظهر للمستخدمين على الفور ما يتعاملون معه.

لن يكون لدى معظم الناس أي فكرة عما يتعاملون معه إذا رأوا QuaternaryHeap . من ناحية أخرى ، إذا رأوا PriorityQueue ، فيجب أن يكون واضحًا ما يتعاملون معه ، حتى لو لم يكن لديهم أي خلفية CS. لن يعرفوا ما هو التنفيذ ، لكن هذا هو الغرض من التوثيق.

ianhays

أولاً - ما هو TKey و TValue في الكود الخاص بك؟

العنصر وأولوية العنصر. يمكنك تبديلها لتكون الأولوية والعنصر المرتبط بهذه الأولوية ، ولكن بعد ذلك يكون لديك مجموعة لا تكون فيها مفاتيح TKeys بالضرورة فريدة (أي السماح بتكرار الأولويات). أنا لست ضد ذلك ، ولكن بالنسبة لي TKey عادة ما يعني التفرد.

المفاتيح - الأشياء المستخدمة في تحديد الأولويات. لا يجب أن تكون المفاتيح فريدة من نوعها. لا يمكننا أن نفترض مثل هذا الافتراض.

النقطة المهمة هي أن عرض فئة العقدة ليس شرطًا لفضح طريقة التحديث.

أعتقد أنه هو: مفتاح تقليل / دعم مفتاح زيادة في المكتبات الأصلية . في هذا الموضوع أيضًا ، هناك فئات مساعدة متضمنة للتعامل مع هياكل البيانات:

أتخيل أن عددًا لا بأس به من المستخدمين سيضطرون إلى إنشاء نوع غلاف باستخدام منطق مقارن مخصص. أتخيل أن هناك أيضًا عددًا لا بأس به من المستخدمين الذين لديهم بالفعل نوع مشابه يريدون وضعه في قائمة الانتظار ذات الأولوية أو نوع بمقارن محدد. السؤال هو أي معسكر هو أكبر من الاثنين.

لا أعرف شيئًا عن الآخرين ، لكنني أجد إنشاء غلاف باستخدام منطق مقارن مخصص في كل مرة أرغب في استخدام قائمة انتظار ذات أولوية لتكون أمرًا فظيعًا جدًا ...

فكرة أخرى - لنفترض أنني أنشأت غلافًا:

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

أطعم PriorityQueue<T> بهذا النوع. لذلك فهي تعطي الأولوية لعناصرها بناءً عليها. لنفترض أنها حددت بعض محددات المفاتيح. مثل هذا التصميم يمكن أن يحطم العمل الداخلي للكومة. يمكنك تغيير الأولويات في أي وقت لأنك مالك العنصر. لا توجد آلية إعلام لتحديث الكومة. ستكون مسؤولاً عن التعامل مع كل ذلك والاتصال به. خطير جدا وليس مستقيم للأمام بالنسبة لي.

في حالة المقبض الذي اقترحته - كان النوع ثابتًا من وجهة نظر المستخدم. ولم تكن هناك مشاكل مع التفرد.

IMO تعجبني فكرة وجود واجهة تفاعلية IHeap ثم واجهة برمجة تطبيقات للفئة تقوم بتنفيذ تلك الواجهة. أنا مع ianhays 'في هذا الأمر لأننا لن نكشف عن 3 واجهات برمجة تطبيقات جديدة لتوفير أنواع مختلفة من الأكوام للعملاء حيث ستكون هناك حاجة إلى عملية مراجعة API ونود التركيز على PriorityQueue مهما كان الاسم.

ثانيًا ، أعتقد أنه ليست هناك حاجة لوجود فئة داخلية / عامة في Node لتخزين القيم في قائمة الانتظار. لا حاجة لتخصيصات إضافية ، يمكننا أن نتبع شيئًا ما مما يفعله IDictionary وعندما نحصل على قيم Enumerable إنشاء KeyValuePairالأشياء إذا اخترنا خيار الحصول على أولوية لكل عنصر نقوم بتخزينه (والذي لا أعتقد أنه أفضل طريقة للذهاب ، فأنا لست مقتنعًا تمامًا بتخزين أولوية لكل عنصر). أفضل طريقة أود الحصول عليها هي الحصول على PriorityQueue<T> (هذا الاسم مخصص فقط لمنحه واحدًا). باستخدام هذا النهج ، يمكن أن يكون لدينا مُنشئ يتبع ما يفعله BCL بالكامل باستخدام IComparer<T> ويقوم بإجراء المقارنة مع هذا المكون لإعطاء الأولوية. لا حاجة لفضح واجهات برمجة التطبيقات التي تمرر أولوية كمعامل.

أعتقد أن حصول بعض واجهات برمجة التطبيقات على أولوية سيجعلها "أقل قابلية للاستخدام" أو أكثر تعقيدًا للعملاء العاديين الذين يرغبون في أن يكونوا الأولوية الافتراضية ، ثم نجعل الأمر أكثر تعقيدًا لفهم فائدة ذلك ، عند منحهم سيكون خيار الحصول على IComparer<T> مخصصًا هو الخيار الأكثر منطقية وسيتبع الإرشادات التي لدينا عبر BCL.

الأسماء هي ما يفعلونه ، وليس كيف يفعلونه.

يتم تسميتها على اسم المفاهيم المجردة وما تحققه للمستخدم ، بدلاً من تنفيذها وكيفية تحقيقها. (مما يعني أيضًا أنه يمكن تحسين تنفيذها لاستخدام تطبيق مختلف إذا كان ذلك أفضل)

يجب أن ينطبق الشيء نفسه على التكديس وقوائم الانتظار بعد ذلك.

Stack عبارة عن مصفوفة غير محدودة لتغيير الحجم ويتم تنفيذ Queue كمخزن مؤقت دائري غير محدود الحجم. تم تسميتهم بعد مفاهيمهم المجردة. المكدس (نوع البيانات المجردة) : Last-In-First-Out (LIFO) ، قائمة الانتظار (نوع البيانات المجردة) : First-In-First-Out (FIFO)

Hashtable ، ArrayList. يبدو أنهم موجودون.

دخول القاموس

نعم ولكن دعنا نتظاهر بأنهم لا يفعلون ذلك ؛ إنها مصنوعات يدوية من زمن أقل حضارة. ليس لديهم نوع أمان وصندوق إذا كنت تستخدم الأوليات أو البنى ؛ لذلك خصص على كل إضافة.

يمكنك استخدام محلل توافق النظام الأساسي terrajobst وسيخبرك : "من فضلك لا"

القائمة هي قائمة ، لكنها ليست قائمة

إنها حقًا قائمة (نوع بيانات مجردة) تُعرف أيضًا باسم تسلسل.

قائمة انتظار الأولوية المتساوية هي نوع مجردة يخبرك بما يحققه في الاستخدام ؛ ليس كيف يتم ذلك في التنفيذ (حيث يمكن أن يكون هناك العديد من التطبيقات المختلفة)

الكثير من المناقشة الرائعة!

تم تصميم المواصفات الأصلية مع وضع بعض المبادئ الأساسية في الاعتبار:

  • الغرض العام - تغطية معظم حالات الاستخدام مع التوازن بين استهلاك وحدة المعالجة المركزية والذاكرة.
  • محاذاة ، قدر الإمكان ، مع الأنماط والاصطلاحات الموجودة في System.Collections.Generic.
  • السماح بإدخالات متعددة لنفس العنصر.
  • استخدم واجهات وفئات مقارنة BCL الحالية (على سبيل المثال ، المقارنة). *
  • مستوى عالٍ من التجريد - تم تصميم المواصفات لحماية المستهلكين من تقنية التنفيذ الأساسية.

تضمين التغريدة
إعادة التسمية إلى "كومة" أو مصطلح آخر محاذي لتنفيذ بنية البيانات لن يطابق معظم اصطلاحات BCL للمجموعات. تاريخيًا ، تم تصميم فئات تجميع .NET لتكون ذات أغراض عامة بدلاً من التركيز على هيكل / نمط البيانات المحدد. كان تفكيري الأصلي هو أنه في وقت مبكر من النظام البيئي لـ .NET ، انتقل مصممو واجهة برمجة التطبيقات عن قصد من "ArrayList" إلى "List"". كان التغيير على الأرجح ناتجًا عن ارتباك مع Array - كان المطور العادي لديك يظن" ArrayList؟ أنا فقط أريد قائمة ، وليس مصفوفة ".

إذا استخدمنا Heap ، فقد يحدث نفس الشيء - سيشاهد العديد من المطورين ذوي المهارات المتوسطة (للأسف) "الكومة" ويخلطون بينها وبين كومة ذاكرة التطبيق (مثل الكومة والمكدس) بدلاً من "كومة بنية البيانات المعممة". سيؤدي انتشار System.Collections.Generic إلى ظهوره في كل اقتراح ذكي لمطوري .NET تقريبًا ، وسوف يتساءلون لماذا يمكنهم تخصيص كومة ذاكرة جديدة :)

بالمقارنة ، فإن PriorityQueue أكثر قابلية للاكتشاف وأقل عرضة للارتباك. يمكنك كتابة "قائمة الانتظار" والحصول على مقترحات لـ PriorityQueue.

طرح عدد قليل من المقترحات والأسئلة حول أولويات العدد الصحيح أو المعلمة العامة للأولوية (TKey ، TPriority ، إلخ). تتطلب إضافة أولوية صريحة من المستهلكين كتابة منطقهم الخاص لتعيين أولوياتهم وزيادة تعقيد واجهة برمجة التطبيقات. باستخدام IComparer المدمجيعزز وظائف BCL الحالية ، وقد فكرت أيضًا في إضافة أحمال زائدة إلى Comparerفي المواصفات لتسهيل تقديم تعبيرات لامدا مخصصة / وظائف مجهولة كمقارنات ذات أولوية. هذا ليس اتفاقية شائعة في BCL ، للأسف.

إذا كان المطلوب أن تكون الإدخالات فريدة ، فإن Enqueue () تتطلب بحثًا فريدًا لإلقاء ArgumentException. بالإضافة إلى ذلك ، من المحتمل أن تكون هناك سيناريوهات صحيحة للسماح بوضع عنصر في قائمة الانتظار أكثر من مرة. هذا التصميم غير الفريد يجعل توفير عملية Update () أمرًا صعبًا حيث لن تكون هناك طريقة لمعرفة الكائن الذي يتم تحديثه. كما أوضحت بعض التعليقات ، سيبدأ هذا في الوصول إلى واجهات برمجة التطبيقات بإرجاع مراجع "العقدة" والتي بدورها ستتطلب (على الأرجح) تخصيصات تحتاج إلى جمع القمامة. حتى إذا تم حل المشكلة ، فسيؤدي ذلك إلى زيادة استهلاك الذاكرة لكل عنصر لقائمة انتظار الأولوية.

في وقت من الأوقات ، كان لدي واجهة IPriorityQueue مخصصة في واجهة برمجة التطبيقات قبل أن أنشر المواصفات. في النهاية قررت رفضه - نمط الاستخدام الذي كنت أهدف إليه هو Enqueue و Dequeue و Iterate. بالفعل مغطاة من قبل مجموعة الواجهة الحالية. التفكير في هذا على أنه قائمة انتظار يتم فرزها داخليًا ؛ طالما أن العناصر تحتفظ بمكانها (الأولي) في قائمة الانتظار بناءً على IComparer، لا يحتاج المتصل أبدًا إلى القلق بشأن كيفية تمثيل الأولوية. في تطبيق المرجع القديم الذي قمت به ، (إذا كنت أتذكر جيدًا!) لم يتم تمثيل الأولوية على الإطلاق. كل شيء نسبي يعتمد على IComparerأو المقارنة.

أنا مدين لك ببعض أمثلة رموز العملاء - كانت خطتي الأصلية هي متابعة تطبيقات BCL الحالية لـ PriorityQueue لاستخدامها كأساس للأمثلة.

في ملخص واجهة برمجة التطبيقات ، يتم توفير المقارنة من خلال مقارنة IComparer المقدمةالمقارنة ، لا يلزم وجود غلاف. غالبًا ما تكون الأولوية جزءًا من النوع ، على سبيل المثال ، سيكون لجدول الوقت وقت التنفيذ كخاصية من النوع.

بموجب OP API المقترح ، يجب تلبية أحد هذه العناصر لاستخدام الفئة:

  • لديك نوع يمكن مقارنته بالفعل بالطريقة التي تريدها
  • قم بلف نوع مع فئة أخرى تحتوي على قيمة الأولوية وتقارن بالطريقة التي تريدها
  • قم بإنشاء IComparer مخصص لنوعك وقم بتمريره عبر المنشئ. تفترض أن القيمة التي تريد أن تمثل الأولوية قد تم عرضها علنًا بالفعل حسب نوعك.

تهدف واجهة برمجة التطبيقات من النوع المزدوج إلى تقليل عبء الخيارين الآخرين ، أليس كذلك؟ كيف يعمل إنشاء PriorityQueue / Heap جديدًا عندما يكون لديك نوع يحتوي بالفعل على الأولوية ؟ كيف تبدو واجهة برمجة التطبيقات؟

أعتقد أنه هو: مفتاح تقليل / دعم مفتاح زيادة في المكتبات الأصلية.

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

في كلتا الحالتين ، يتم تحديث العنصر المرتبط بالأولوية المحددة. لماذا ArrayHeapNode مطلوب للتحديث؟ ما الذي يحققه ولا يمكن تحقيقه من خلال أخذ TKey / TValue مباشرة؟

ianhays

في كلتا الحالتين ، يتم تحديث العنصر المرتبط بالأولوية المحددة. لماذا ArrayHeapNode مطلوب للتحديث؟ ما الذي يحققه ولا يمكن تحقيقه من خلال أخذ TKey / TValue مباشرة؟

على الأقل مع الكومة الثنائية (أكثر ما أعرفه جيدًا) ، إذا كنت تريد تحديث أولوية بعض القيمة وتعرف موقعها في الكومة ، فيمكنك القيام بذلك بسرعة (في وقت O (تسجيل ن)).

لكن إذا كنت تعرف القيمة والأولوية فقط ، فستحتاج أولاً إلى إيجاد القيمة ، وهي بطيئة (O (n)).

من ناحية أخرى ، إذا لم تكن بحاجة إلى التحديث بكفاءة ، فإن هذا التخصيص الواحد لكل عنصر في الكومة يمثل عبئًا محضًا.

أود أن أرى حلاً حيث تدفع هذه النفقات العامة فقط عندما تحتاجها ، ولكن قد لا يكون ذلك ممكنًا للتنفيذ وقد لا تبدو واجهة برمجة التطبيقات الناتجة جيدة.

تهدف واجهة برمجة التطبيقات من النوع المزدوج إلى تقليل عبء الخيارين الآخرين ، أليس كذلك؟ كيف يعمل إنشاء PriorityQueue / Heap جديدًا عندما يكون لديك نوع يحتوي بالفعل على الأولوية؟ كيف تبدو واجهة برمجة التطبيقات؟

هذه نقطة جيدة ، فهناك فجوة في واجهة برمجة التطبيقات الأصلية للسيناريوهات حيث يكون للمستهلك بالفعل قيمة أولوية (أي عدد صحيح) يتم الاحتفاظ بها بشكل منفصل عن القيمة. الجانب الآخر هو أن ملفأسلوب API سيعقد جميع الأنواع القابلة للمقارنة جوهريًا. منذ سنوات ، كتبت مكون جدولة خفيف الوزن للغاية يحتوي على فئة ScheduledTask الداخلية الخاصة به. نفذت كل ScheduledTask IComparer. قم بإلقائهم في قائمة انتظار الأولوية ، على ما يرام.

قائمة مرتبةيستخدم تصميم Key / Value ووجدت نفسي أتجنب استخدامه كنتيجة لذلك. يتطلب أن تكون المفاتيح فريدة ، ومتطلب المعتاد هو "لدي قيم N أحتاج إلى الاحتفاظ بها مرتبة في قائمة ، والتأكد من بقاء القيم العرضية التي أضيفها مرتبة". "المفتاح" ليس جزءًا من تلك المعادلة ، والقائمة ليست قاموسًا.

بالنسبة لي ، ينطبق نفس المبدأ على قوائم الانتظار. قائمة الانتظار ، تاريخيًا ، هي بنية بيانات أحادية الأبعاد.

من المسلم به أن معرفتي الحديثة بمكتبة C ++ std صدئة بعض الشيء ، ولكن يبدو أن std :: priority_queue لها دفع ، فرقعة ، وأخذ مقارن كمعامل نموذجي (عام). طاقم مكتبة C ++ القياسي حساس للأداء بقدر ما يمكنك الحصول عليه :)

لقد أجريت فحصًا سريعًا جدًا لقائمة الانتظار ذات الأولوية وتطبيقات الكومة في عدد قليل من لغات البرمجة الآن - تعمل C ++ و Java و Rust و Go جميعها بنوع واحد (على غرار واجهة برمجة التطبيقات الأصلية المنشورة هنا). تظهر نظرة خاطفة على تطبيقات قائمة الانتظار الأكثر شيوعًا / الأولوية في NPM نفسها.

pgolebiowski لا تفهموني بشكل خاطئ ، يجب أن يكون هناك تطبيقات محددة للأشياء التي تم تسميتها صراحة بعد تنفيذها المحدد.

ومع ذلك ، هذا عندما تعرف بنية البيانات المحددة التي تريدها والتي تتطابق مع أهداف الأداء التي تسعى إليها ولديك المقايضات التي ترغب في قبولها.

بشكل عام ، تغطي مجموعات إطار العمل 90٪ من الاستخدامات التي تريد سلوكًا عامًا فيها. ثم إذا كنت تريد سلوكًا أو تنفيذًا محددًا جدًا ، فمن المحتمل أن تذهب إلى مكتبة تابعة لجهة خارجية ؛ ونأمل أن يتم تسميتهم بعد التنفيذ حتى تعرف أنه يلبي احتياجاتك.

فقط لا تريد ربط أنواع السلوك العامة بتطبيق معين ؛ لأنه من الغريب أن يتغير التنفيذ لأن اسم النوع يجب أن يظل كما هو ولن يتطابق.

هناك العديد من المخاوف التي تحتاج إلى مناقشة ، ولكن لنبدأ بالمشكلة التي لها حاليًا التأثير الأكبر على الأجزاء التي لا نتفق بشأنها: تحديث العناصر وإزالتها .

كيف تريد دعم هذه العمليات بعد ذلك؟ علينا أن ندرجها ، فهي أساسية. في Java ، حذفها المصممون ، ونتيجة لذلك:

  1. هناك الكثير من الأسئلة في المنتديات حول كيفية تنفيذ الحلول البديلة بسبب الميزات المفقودة.
  2. هناك تطبيقات تابعة لجهات خارجية لقائمة انتظار الكومة / الأولوية لاستبدال تطبيق الطرف الأول ، لأنه عديم الفائدة إلى حد كبير.

هذا مجرد مثير للشفقة. هل هناك أي شخص يريد حقًا متابعة مثل هذا الطريق؟ سأخجل من إطلاق مثل هذا الهيكل المعطل للبيانات.

تطمئن pgolebiowski أن كل شخص هنا لديه أفضل النوايا للمنصة. لا أحد يريد شحن واجهات برمجة التطبيقات المعطلة. نريد أن نتعلم من أخطاء الآخرين ، لذا يرجى الاستمرار في جلب هذه المعلومات المفيدة ذات الصلة (مثل تلك المتعلقة بقصة Java).

ومع ذلك ، أود أن أشير إلى أمرين:

  • لا تتوقع التغييرات بين عشية وضحاها. هذه مناقشة التصميم. نحن بحاجة إلى إيجاد توافق في الآراء. نحن لا نتسرع في واجهات برمجة التطبيقات. يجب الاستماع إلى كل رأي والنظر فيه ، ولكن ليس هناك ما يضمن تنفيذ / قبول رأي الجميع. إذا كان لديك مدخلات ، فقدمها ، وادعمها بالبيانات والأدلة. استمع أيضًا إلى حجج الآخرين ، واعترف بها. قدم أدلة ضد رأي الآخرين إذا كنت لا توافق. في بعض الأحيان ، اتفق على حقيقة وجود خلاف حول بعض النقاط. ضع في اعتبارك أن SW ، بما في ذلك. تصميم واجهة برمجة التطبيقات ليس شيئًا أسود أو أبيض / صحيحًا أو خاطئًا.
  • دعونا نجعل المناقشة حضارية. دعونا لا نستخدم كلمات وعبارات قوية. دعونا نختلف مع النعمة ودعونا نواصل المناقشة تقنية. يمكن للجميع الرجوع أيضًا إلى مدونة قواعد السلوك الخاصة بالمساهمين . نحن ندرك ونشجع الشغف تجاه .NET ، ولكن دعونا نتأكد من عدم الإساءة لبعضنا البعض.
  • إذا كانت لديك أي مخاوف / أسئلة بخصوص سرعة مناقشة التصميم وردود الفعل وما إلى ذلك ، فلا تتردد في التواصل معي مباشرة (بريدي الإلكتروني موجود في ملفي الشخصي في GH). يمكنني المساعدة في توضيح التوقعات والافتراضات والمخاوف علنًا أو خارج الإنترنت إذا لزم الأمر.

لقد سألت للتو كيف تريدون يا رفاق تصميم شيء التحديث / الإزالة إذا كنت لا تحب النهج المقترح ... هذا هو الاستماع للآخرين والبحث عن الإجماع ، على ما أعتقد.

لا أشك في حسن نواياك! في بعض الأحيان يكون من المهم كيف تسأل - فهي تؤثر على كيفية إدراك الناس للنص على الجانب الآخر. النص خالٍ من المشاعر ، لذلك يمكن فهم الأشياء بشكل مختلف عند كتابتها. اللغة الإنجليزية كلغة ثانية تعكر الأمور بشكل أكبر وعلينا جميعًا أن نكون على دراية بذلك. سأكون سعيدًا بالدردشة حول التفاصيل في وضع عدم الاتصال إذا كنت مهتمًا ... دعنا نوجه المناقشة هنا مرة أخرى إلى المناقشة الفنية ...

سنتي في مناقشة Heap مقابل PriorityQueue: كلا النهجين صالحان ومن الواضح أنهما لهما فوائد وعيوب.

ومع ذلك ، يبدو "PriorityQueue" أكثر اتساقًا مع أسلوب .NET الحالي. اليوم المجموعات الأساسية هي Listالقاموس، كومة، طابور، HashSet، SortedD Dictionaryو SortedSet. تمت تسميتها بعد الوظائف والدلالات ، وليس الخوارزمية. HashSetهو الخارج الوحيد ، ولكن حتى هذا يمكن تبريره على أنه يتعلق بدلالات المساواة المحددة (مقارنة بـ SortedSet). بعد كل شيء ، لدينا الآن مجموعة ImmutableHashSetالذي يقوم على شجرة تحت الغطاء.

سيكون من الغريب أن تتغلب مجموعة واحدة على الاتجاه هنا.

أعتقد أن PriorityQueue مع مُنشئ إضافي: PriorityQueue(IHeap) يمكن أن يكون حلاً. يمكن للمنشآت التي لا تحتوي على معلمة IHeap استخدام Heap الافتراضي.
في هذه الحالة ، PrioriryQueueسيمثل نوع البيانات المجردة (مثل معظم مجموعات C #) وتنفيذ IPriorityQueueواجهة ولكن يمكن استخدام تطبيقات كومة مختلفة مثل اقترح pgolebiowski :

فئة ArrayHeap: IHeap {}
class PairingHeap: IHeap {}
فئة FibonacciHeap: IHeap {}
فئة BinomialHeap: IHeap {}

نعم. هناك الكثير من الأصوات المختلفة. مررت بالمناقشة مرة أخرى وصقلت توجهي. أنا أيضا أعالج الاهتمامات المشتركة. يستخدم النص أدناه الاقتباسات من الوظائف أعلاه.

هدفنا

الهدف النهائي من هذه المناقشة بأكملها هو توفير مجموعة محددة من الوظائف لإسعاد المستخدم. إنها حالة شائعة جدًا أن المستخدم لديه مجموعة من العناصر ، حيث يكون لبعضها أولوية أعلى من البعض الآخر. في النهاية ، يريدون الاحتفاظ بهذه المجموعة من العناصر بترتيب معين حتى يتمكنوا من أداء العمليات التالية بكفاءة:

  • استرجع العنصر بأولوية قصوى (وكن قادرًا على إزالته).
  • أضف عنصرًا جديدًا إلى المجموعة.
  • إزالة عنصر من المجموعة.
  • تعديل عنصر في المجموعة.
  • دمج مجموعتين.

مكتبات قياسية أخرى

حاول مؤلفو المكتبات القياسية الأخرى أيضًا دعم هذه الوظيفة. في هذا القسم ، سأشير إلى كيفية حلها في Python و Java و C ++ و Go و Swift و Rust .

لا يدعم StackOverflow . هذا واحد من العديد والعديد من الأسئلة المشابهة عبر الإنترنت. لقد فشل المصممون.

شيء آخر جدير بالملاحظة هو أن كل مكتبة توفر هذه الوظيفة الجزئية من خلال تنفيذ كومة ثنائية . حسنًا ، لقد ثبت أنه ليس التنفيذ الأكثر أداءً في الحالة العامة (إدخال عشوائي). الأنسب للحالة العامة هو كومة رباعية (شجرة كاملة مكونة من 4 آرات بترتيب كومة ضمنية ، مخزنة كمصفوفة). إنه غير معروف بشكل كبير وربما هذا هو السبب الذي دفع المصممين إلى استخدام كومة ثنائية بدلاً من ذلك. ولكن لا يزال - خيارًا سيئًا آخر ، على الرغم من أنه أقل خطورة.

ماذا نتعلم من ذلك؟

  • إذا أردنا أن يكون عملاؤنا سعداء ونمنعهم من تنفيذ هذه الوظيفة بأنفسهم مع تجاهل بنية البيانات الرائعة ، فيجب أن نقدم دعمًا لتعديل العناصر المدرجة بالفعل في المجموعة.
  • يجب ألا نفترض أنه نظرًا لأن بعض الوظائف تم تقديمها بطريقة ما في مكتبة قياسية ما ، يجب أن نفعل الشيء نفسه ، لأنه أصبح معروفًا الآن على نطاق واسع والمستخدمون معتادون على ذلك . لاحظ الجزء الأخير من الجملة.

النهج المقترح

أشعر بشدة أنه يجب علينا توفير:

  • واجهة IHeap<T>
  • Heap<T>

من الواضح أن واجهة IHeap ستتضمن طرقًا لتحقيق جميع العمليات الموضحة في بداية هذا المنشور. ستكون فئة Heap ، التي تم تنفيذها باستخدام كومة رباعية ، هي الحل الأمثل في 98٪ من الحالات. سيعتمد ترتيب العناصر على IComparer<T> تم تمريره إلى المُنشئ أو الترتيب الافتراضي إذا كان النوع قابلاً للمقارنة بالفعل.

التبرير

  • يمكن للمطورين كتابة منطقهم على واجهة. أفترض أن الجميع يعرف مدى أهمية ذلك ولن يخوض في التفاصيل. اقرأ: مبدأ انعكاس التبعية ، حقن التبعية ، التصميم بالعقد ، قلب السيطرة .
  • يمكن للمطورين توسيع هذه الوظيفة لتلبية احتياجاتهم المخصصة من خلال توفير تطبيقات كومة أخرى. يمكن تضمين مثل هذه التطبيقات في مكتبات الجهات الخارجية مثل PowerCollections . بمجرد الرجوع إلى مثل هذه المكتبة ، يمكنك ببساطة ضخ الكومة المخصصة الخاصة بك في أي منطق يأخذ IHeap كمدخل. بعض الأمثلة على الأكوام الأخرى التي تتصرف بشكل أفضل من الكومة الرباعية في بعض الحالات المحددة هي: الكومة المزاوجة ، الكومة ذات الحدين ، الكومة الثنائية المحبوبة لدينا.
  • إذا كان المطور يحتاج فقط إلى أداة تنجز المهمة دون الحاجة إلى التفكير في النوع الأفضل ، فيمكنه ببساطة استخدام تنفيذ الأغراض العامة Heap . هذا هو الأمثل نحو 98٪ من حالات الاستخدام.
  • نضيف إلى القيمة الكبيرة لنظام .NET البيئي فيما يتعلق بتقديم خيار فردي بخصائص أداء لائقة جدًا ، ومفيد لمعظم حالات الاستخدام ، وفي نفس الوقت السماح بامتدادات عالية الأداء لمن يحتاجون إليه ومستعدون للحفر أعمق وتعلم المزيد / اتخذ خيارات ومفاضلات مدروسة.
  • النهج المقترح يعكس الاصطلاحات الحالية:

    • ISet و HashSet

    • IList و List

    • IDictionary و Dictionary

  • صرح بعض الأشخاص بأنه يجب علينا تسمية الفئات بناءً على ما تفعله مثيلاتهم ، وليس كيفية قيامهم بذلك. هذا ليس صحيحا تماما إنه اختصار شائع للقول إنه يجب علينا تسمية الفئات بعد سلوكها. إنه ينطبق بالفعل في العديد من الحالات. ومع ذلك ، هناك حالات لا يكون فيها هذا هو النهج المناسب. أبرز الأمثلة هي اللبنات الأساسية - مثل الأنواع الأولية أو أنواع التعداد أو هياكل البيانات. المبدأ هو ببساطة اختيار الأسماء ذات المعنى (أي التي لا لبس فيها للمستخدم). ضع في الاعتبار حقيقة أن الوظيفة التي نناقشها يتم توفيرها دائمًا ككومة - سواء كانت Python أو Java أو C ++ أو Go أو Swift أو Rust. الكومة هي واحدة من أبسط هياكل البيانات. Heap هو حقًا واضح وواضح. كما أنه يتوافق مع Stack و Queue و List و Array . تم اتباع نفس الأسلوب مع التسمية في أحدث المكتبات القياسية (Go ، Swift ، Rust) - فهي تعرض كومة بشكل صريح.

pgolebiowski لا أرى كيف تم تسمية Heap<T> / IHeap<T> مثل Stack<T> ، Queue<T> ، و / أو List<T> ؟ لا يشرح أي من هذه الأسماء كيف يتم تنفيذها داخليًا (مصفوفة من T كما يحدث).

تضمين التغريدة

لا يوضح Heap كيف يتم تنفيذه داخليًا أيضًا. لا أستطيع أن أفهم لماذا لكثير من الناس كومة يتبع مباشرة لتنفيذ محددة. هناك العديد من المتغيرات للأكوام التي تشترك في نفس واجهة برمجة التطبيقات ، لتبدأ بـ:

  • أكوام د آري ،
  • 2-3 أكوام ،
  • أكوام اليسار ،
  • أكوام ناعمة ،
  • أكوام ضعيفة ،
  • أكوام B ،
  • أكوام الجذور ،
  • أكوام الانحراف ،
  • أكوام الاقتران ،
  • أكوام فيبوناتشي ،
  • أكوام ذات الحدين ،
  • أكوام ترتيب الاقتران ،
  • أكوام الزلزال ،
  • أكوام الانتهاك.

إن القول بأننا نتعامل مع كومة لا يزال مجرّدًا للغاية. في الواقع ، حتى القول بأننا نتعامل مع كومة رباعية هو أمر تجريدي - يمكن تنفيذه إما كهيكل بيانات ضمني يعتمد على مصفوفة من T (مثل Stack<T> ، Queue<T> و List<T> ) أو صريح (باستخدام العقد والمؤشرات).

باختصار ، Heap<T> مشابه جدًا لـ Stack<T> ، Queue<T> ، و List<T> ، لأنه بنية بيانات أولية ، لبنة بناء مجردة أساسية ، يمكن تنفيذها بعدة طرق. بالإضافة إلى ذلك ، كما يحدث ، يتم تنفيذها جميعًا باستخدام مصفوفة من T تحتها. أجد هذا التشابه قويًا حقًا.

هل هذا منطقي؟

للتسجيل ، أنا غير مبال بشأن التسمية. ربما يفضل الأشخاص الذين اعتادوا استخدام مكتبة C ++ القياسية _priority_queue_. قد يفضل الأشخاص الذين تم تعليمهم حول هياكل البيانات _Heap_. إذا اضطررت إلى التصويت ، سأختار _heap_ ، على الرغم من أنها قريبة من رمي عملة معدنية بالنسبة لي.

pgolebiowski لقد سيئي . نعم ، لا يذكر Heap<T> كيف يتم تنفيذه داخليًا.

نعم الكومة هي بنية بيانات صالحة ، لكن الكومة! = قائمة انتظار الأولوية. كلاهما يعرض أسطح API مختلفة ويستخدمان لأفكار مختلفة. Heap<T> / IHeap<T> من أنواع البيانات المستخدمة داخليًا بواسطة (الأسماء النظرية فقط) PriorityQueue<T> / IPriorityQueue<T> .

تضمين التغريدة
من حيث كيفية تنظيم عالم علوم الكمبيوتر ، نعم. فيما يلي مستويات التجريد:

  • التنفيذ : الكومة الرباعية الضمنية بناءً على مصفوفة
  • التجريد : الكومة الرباعية
  • التجريد : عائلة أكوام
  • التجريد : عائلة قوائم الانتظار ذات الأولوية

ونعم ، بوجود IHeap و Heap ، فإن تنفيذ PriorityQueue سيكون في الأساس:

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...
}

لنقم بتشغيل شجرة قرار هنا.

يمكن أيضًا تنفيذ قائمة انتظار الأولوية باستخدام بنية بيانات مختلفة عن شكل من أشكال كومة (نظريًا). هذا يجعل تصميم PriorityQueue مثل التصميم أعلاه قبيحًا جدًا ، لأنه ثابت تجاه عائلة الأكوام فقط. إنه أيضًا غلاف رقيق جدًا حول IHeap . ينتج عن هذا سؤال - _لماذا لا تستخدم عائلة الأكوام بدلاً من ذلك_؟

لقد تركنا حلًا واحدًا - إصلاح قائمة الانتظار ذات الأولوية لتنفيذ محدد لكومة كومة رباعية ، بدون مساحة لواجهة IHeap . أشعر أنه يمر عبر مستويات كثيرة جدًا من التجريد ويقتل كل الفوائد الرائعة لامتلاك واجهة.

لقد عدنا مع اختيار التصميم من منتصف المناقشة - لدينا PriorityQueue و IPriorityQueue . ولكن بعد ذلك سنحصل بشكل أساسي على:

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

لا يبدو الأمر قبيحًا فحسب ، بل إنه خاطئ أيضًا من الناحية المفاهيمية - فهذه ليست أنواعًا من قوائم الانتظار ذات الأولوية ولا تشترك في نفس واجهة برمجة التطبيقات (كما لاحظ SamuelEnglard بالفعل). أعتقد أننا يجب أن نتمسك بالأكوام ، عائلة ضخمة بما يكفي لتكون فكرة مجردة لهم. سوف نحصل على:

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

والمقدمة بواسطتنا class Heap : IHeap {} .


راجع للشغل ، قد يجد شخص ما ما يلي مفيدًا:

| استعلام جوجل | النتائج |
| : --------------------------------------: | : -----: |
| "هيكل البيانات" "قائمة انتظار الأولوية" | 172000 |
| "بنية البيانات" "كومة" | 430،000 |
| "بنية البيانات" "قائمة الانتظار" - "قائمة انتظار الأولوية" | 496000 |
| "بنية البيانات" "قائمة الانتظار" | 530،000 |
| "بنية البيانات" "مكدس" | 577000 |

pgolebiowski أشعر أنني أذهب ذهابًا وإيابًا هنا لذا

karelzsafern كيف تشعر حيال ما سبق؟ هل يمكننا إصلاح أنفسنا على نهج IHeap + Heap حتى أتمكن من تقديم اقتراح محدد لواجهة برمجة التطبيقات؟

أشكك في الحاجة إلى واجهة هنا (سواء كانت IHeap أو IPriorityQueue ). نعم ، هناك العديد من الخوارزميات المختلفة التي يمكن استخدامها نظريًا لتنفيذ بنية البيانات هذه. ومع ذلك ، يبدو من غير المحتمل أن يتم شحن إطار العمل بأكثر من واحد ، وفكرة أن كتاب المكتبات يحتاجون حقًا إلى واجهة متعارف عليها بحيث يمكن للأطراف المختلفة التنسيق في كتابة تطبيقات كومة متوافقة متقاطعة لا يبدو ذلك مرجحًا.

علاوة على ذلك ، بمجرد إصدار الواجهة ، لا يمكن تغييرها أبدًا بسبب التوافق. هذا يعني أن الواجهة تميل إلى التخلف عن الفئات الملموسة من حيث الوظائف (مشكلة في IList و IDictionary اليوم). في المقابل ، إذا تم إصدار فئة Heap وكان هناك صخب شديد لواجهة IHeap ، أعتقد أنه يمكن إضافة الواجهة دون مشاكل.

madelson أوافق على أن إطار العمل على الأرجح لا يحتاج إلى شحن أكثر من تنفيذ واحد

لا أرى أي نقاط سلبية في الترميز للواجهة. يمكن لأولئك الذين لا يهتمون بها أن يتجاهلوها ويذهبوا مباشرة إلى التنفيذ الملموس. أولئك الذين يهتمون يمكنهم استخدام أي تنفيذ من اختيارهم. إنه اختيار. وهو خيار أريده.

pgolebiowski الآن بعد أن سألت ، فإليك رأيي الشخصي (أنا متأكد إلى حد ما من أن المراجعين / المهندسين المعماريين الآخرين لواجهة برمجة التطبيقات سيشاركونها كما راجعت من أجل الرأي مع عدد قليل):
يجب أن يكون الاسم PriorityQueue ، ويجب ألا نقدم واجهة IHeap . يجب أن يكون هناك تنفيذ واحد بالضبط (على الأرجح عبر بعض الكومة).
واجهة IHeap هي سيناريو خبير متقدم للغاية - أقترح نقلها إلى مكتبة PowerCollections (سيتم إنشاؤها في النهاية) أو أي مكتبة أخرى.
إذا أصبحت المكتبة وواجهة IHeap شائعة حقًا ، فيمكننا تغيير رأينا لاحقًا وإضافة IHeap بناءً على الطلب (عبر التحميل الزائد للمُنشئ) ، لكنني لا أعتقد أنه مفيد / متوافق مع بقية BCL بما يكفي لتبرير تعقيد إضافة واجهة جديدة الآن. ابدأ بسيطًا ، وتوسع إلى معقد فقط إذا لزم الأمر.
... فقط سنتان (شخصية)

نظرًا لاختلاف الآراء ، أقترح النهج التالي للمضي قدمًا في الاقتراح (والذي اقترحته في وقت سابق من هذا الأسبوع على ianhays ، وبالتالي بشكل غير مباشر إلىsafern):

  • قدم اقتراحين بديلين - أحدهما بسيط كما وصفته أعلاه والآخر مع Heaps كما اقترحت ، دعنا نحضره إلى مراجعة API ، ونناقشه هناك ونتخذ قرارًا هناك.
  • إذا رأيت شخصًا واحدًا على الأقل في تلك المجموعة يصوت لصالح اقتراح Heaps ، فسيسعدني إعادة النظر في وجهة نظري.

... أحاول أن أكون شفافًا تمامًا بشأن رأيي (الذي طلبته) ، من فضلك لا تعتبره مثبطًا للتشجيع أو تراجعه - دعنا نرى كيف يسير اقتراح واجهة برمجة التطبيقات.

تضمين التغريدة

يجب أن يكون الاسم PriorityQueue

أي حجة؟ سيكون لطيفًا على الأقل إذا تناولت ما كتبته أعلاه بدلاً من مجرد قول لا .

أعتقد أنك سميته جيدًا سابقًا: _إذا كان لديك مدخلات ، فقدمها ، وادعمها بالبيانات والأدلة. استمع أيضًا إلى حجج الآخرين ، واعترف بها. قدم أدلة ضد رأي الآخرين إذا كنت لا توافق.

بالإضافة إلى ذلك ،

أوه ، كان هناك جدال في هذا النقاش لماذا:

إذا ذهبنا مع Heap ، فإن العديد من المطورين ذوي المهارات المتوسطة سيرون (للأسف) "الكومة" ويخلطون بينها وبين كومة ذاكرة التطبيق (مثل الكومة والمكدس) بدلاً من "كومة بنية البيانات المعممة". [...] سوف يتساءلون لماذا يمكنهم تخصيص كومة ذاكرة جديدة.

😛

يجب ألا نقدم واجهة IHeap . واجهة IHeap هي سيناريو خبير متقدم للغاية - أقترح نقلها إلى مكتبة PowerCollections.

bendono كتب تعليقًا لطيفًا جدًا على هذا. أراد safern أيضًا دعم التنفيذ بواجهة. وعدد قليل من الآخرين.

ملاحظة أخرى - لست متأكدًا من كيفية تخيلك لنقل واجهة إلى مكتبة تابعة لجهة خارجية. كيف يمكن للمطورين ربما كتابة التعليمات البرمجية ضد تلك الواجهة واستخدام وظائف لدينا؟ سيضطرون إما إلى التمسك بوظائفنا التي لا تتعلق بالواجهة أو تجاهلها تمامًا ، ولا يوجد خيار آخر ، فهو حصري بشكل متبادل. بعبارة أخرى - لن يكون حلنا قابلاً للتوسيع على الإطلاق ، مما ينتج عنه استخدام الأشخاص إما لحلنا المعطل أو مكتبة تابعة لجهات خارجية ، بدلاً من الاعتماد على نفس النواة المعمارية في كلتا الحالتين . هذا ما لدينا في مكتبة Java القياسية وحلول الجهات الخارجية.

ولكن مرة أخرى ، لقد علقت بشكل جيد على هذا: لا أحد يريد شحن واجهات برمجة التطبيقات المعطلة.

نظرًا لاختلاف الآراء ، أقترح اتباع نهج للمضي قدمًا في الاقتراح. [...] تقديم اقتراحين بديلين [...] دعنا نحضره إلى مراجعة API ، ونناقشه هناك ونتخذ قرارًا هناك. إذا رأيت شخصًا واحدًا على الأقل في تلك المجموعة يصوت لصالح اقتراح Heaps ، فسيسعدني إعادة النظر في وجهة نظري.

كان هناك الكثير من النقاش حول أجزاء مختلفة من API أعلاه. عالجت:

  • PriorityQueue مقابل Heap
  • إضافة واجهة
  • دعم تحديث / إزالة العناصر من المجموعة

لماذا لا يستطيع هؤلاء الأشخاص الذين تفكر فيهم أن يساهموا فقط في هذه المناقشة؟ لماذا يجب أن نبدأ بدلاً من ذلك مرة أخرى؟

إذا ذهبنا مع Heap ، فإن العديد من المطورين ذوي المهارات المتوسطة سيرون (للأسف) "الكومة" ويخلطون بينها وبين كومة ذاكرة التطبيق (مثل الكومة والمكدس) بدلاً من "كومة بنية البيانات المعممة".

نعم ، هذا أنا. أنا علمت نفسي تمامًا في البرمجة ، لذلك ليس لدي أي فكرة عما قيل عندما دخلت " Heap " المناقشة. ناهيك عن أنه حتى فيما يتعلق بالمجموعة ، فإن عبارة "كومة من الأشياء" بالنسبة لي ستعني بشكل حدسي أنها غير مرتبة في كل الموضة.

لا أصدق أن هذا يحدث بالفعل ...

أي حجة؟ سيكون لطيفًا على الأقل إذا تناولت ما كتبته أعلاه بدلاً من مجرد قول لا.

إذا قرأت إجابتي ، يمكنك ملاحظة أنني ذكرت الحجج الرئيسية لموقفي:

  • واجهة IHeap هي سيناريو خبير متقدم للغاية
  • لا أعتقد أنه مفيد / يتماشى مع بقية BCL بدرجة كافية لتبرير تعقيد إضافة واجهة جديدة الآن

نفس الحجج التي تكررت على الموضوع عدة مرات IMO. أنا فقط لخصتهم

. من فضلك اقرأ ما كتبته.

كنت أراقب هذا الموضوع بنشاط طوال الوقت. قرأت كل الحجج والنقاط. هذا هو رأيي الموجز ، رغم قراءة (وفهم) كل ما لديكم من وجهات نظر الآخرين.
يبدو أنك متحمس للموضوع. هذا عظيم. ومع ذلك ، لدي شعور بأننا وصلنا إلى موقف عندما نكرر فقط الحجج المماثلة من كل جانب ، وهو ما لن يقود إلى أبعد من ذلك بكثير - ولهذا السبب أوصيت باقتراحين والحصول على مزيد من التعليقات عليهما من مجموعة أكبر من الخبرة مراجعو BCL API (راجع للشغل: أنا لا أعتبر نفسي مراجع API متمرسًا حتى الآن).

كيف يمكن للمطورين كتابة التعليمات البرمجية الخاصة بهم مقابل تلك الواجهة واستخدام وظائفنا؟

قد يقوم المطورون الذين يهتمون بالسيناريو المتقدم IHeap بالإشارة إلى الواجهة والتنفيذ من مكتبة الطرف الثالث. كما قلت ، إذا ثبت أنها شائعة ، فيمكننا التفكير في نقلها إلى CoreFX لاحقًا.
والخبر السار هو أن إضافة IHeap لاحقًا أمر ممكن تمامًا - فهو يضيف بشكل أساسي حملًا زائدًا واحدًا للمُنشئ على PriorityQueue .
نعم ، إنها ليست مثالية من وجهة نظرك ، لكنها لا تمنع الابتكار الذي تعتبره مهمًا في المستقبل. أعتقد أنه حل وسط معقول.

لماذا لا يستطيع هؤلاء الأشخاص الذين تفكر فيهم أن يساهموا فقط في هذه المناقشة؟

مراجعة API عبارة عن اجتماع مع مناقشة نشطة ، وعصف ذهني ، وترجيح جميع الزوايا. في كثير من الحالات ، يكون الأمر أكثر إنتاجية / كفاءة من التكرار في مشكلات GitHub. انظر dotnet / corefx # 14354 وسابقتها dotnet / corefx # 8034 - مناقشة طويلة جدًا ، وعدة آراء مختلفة مع تعديلات gazillion يصعب تتبعها ، ولا يوجد استنتاج ، أثناء المناقشة الرائعة ، وأيضًا إضاعة وقت غير تافه لعدد غير قليل من الأشخاص ، حتى جلسنا وتحدثنا عنها وتوصلنا الى توافق.
إن وجود مراجعي واجهة برمجة التطبيقات يراقبون كل مشكلة من مشكلات واجهة برمجة التطبيقات ، أو حتى تلك المشاغبين فقط لا يتسع نطاقه بشكل جيد.

لماذا يجب أن نبدأ بدلاً من ذلك مرة أخرى؟

لن نبدأ مرة أخرى. لماذا تعتقد هذا؟
سننهي مراجعة واجهة برمجة التطبيقات على المستوى الأول (مستوى مالك المنطقة) عن طريق إرسال أكثر اقتراحين شيوعًا مع الإيجابيات / العيوب إلى مستوى مراجعة API التالي.
إنه نهج الموافقات / المراجعات الهرمي. إنه مشابه لمراجعات الأعمال - لا يشرف نواب الرئيس / الرؤساء التنفيذيون الذين يتمتعون بصلاحيات اتخاذ القرار على كل مناقشة حول كل مشروع في شركتهم ، ويطلبون من فرقهم / تقاريرهم تقديم مقترحات للقرارات الأكثر تأثيرًا أو عدوى لإجراء مزيد من المناقشة بشأنها. يجب على الفرق / التقارير تلخيص المشكلة وتقديم إيجابيات / عيوب الحلول البديلة.

إذا كنت تعتقد أننا لسنا مستعدين لتقديم المقترحين النهائيين مع الإيجابيات والسلبيات ، لأن هناك أشياء لم يتم ذكرها في هذا الموضوع حتى الآن ، فلنواصل المناقشة ، حتى يكون لدينا عدد قليل من المرشحين البارزين لمراجعتها في اليوم التالي مستوى مراجعة API.
شعرت أن كل ما يجب أن يقال قد قيل.
من المنطقي؟

يجب أن يكون الاسم PriorityQueue

أي حجة؟

إذا قرأت إجابتي ، يمكنك ملاحظة أنني ذكرت الحجج الرئيسية لموقفي.

الجيز ... كنت أشير إلى ما نقلته (من الواضح ) - أنت قررت الذهاب مع قائمة انتظار الأولوية بدلاً من كومة. ونعم ، لقد قرأت إجابتك - إنها تحتوي بالضبط على 0٪ من الحجج المؤيدة لذلك.

كنت أراقب هذا الموضوع بنشاط طوال الوقت. قرأت كل الحجج والنقاط. هذا هو رأيي الموجز ، رغم قراءة (وفهم) كل ما لديكم من وجهات نظر الآخرين.

تحب أن تكون قطعيًا ، لقد لاحظت ذلك من قبل. أنت تقر فقط بوجود النقاط.

كيف يمكن للمطورين كتابة التعليمات البرمجية الخاصة بهم مقابل تلك الواجهة واستخدام وظائفنا؟

قد يشير المطورون المهتمون بالسيناريو المتقدم لـ IHeap إلى الواجهة والتنفيذ من مكتبة الطرف الثالث. كما قلت ، إذا ثبت أنها شائعة ، فيمكننا التفكير في نقلها إلى CoreFX لاحقًا.

هل تعلم أنك فقط تكرر كلماتي هنا ولا تتناول على الإطلاق المشكلة التي قدمتها كنتيجة لما سبق؟ كتبت:

سيضطرون إما إلى التمسك بوظائفنا التي لا تتعلق بالواجهة أو تجاهلها تمامًا ، ولا يوجد خيار آخر ، فهو حصري بشكل متبادل.

سأجعل هذا واضحا جدا بالنسبة لك. سيكون هناك مجموعتان منفصلتان من الناس:

  1. مجموعة واحدة تستخدم وظائفنا. لا يحتوي على واجهة ولا يمكن تمديده ، وبالتالي فهو غير متصل بما يتم توفيره في مكتبات الطرف الثالث.
  2. المجموعة الثانية التي تتجاهل تمامًا وظائفنا وتستخدم حلولًا من جهات خارجية بحتة.

المشكلة هنا ، كما قلت ، هي أن وهم ينتجون كودًا لا يعمل معًا ، لأنه لا يوجد جوهر معماري مشترك. _ قاعدة التعليمات البرمجية غير متوافقة _. لا يمكنك التراجع عنها لاحقًا.

والخبر السار هو أن إضافة IHeap لاحقًا قابلة للتنفيذ تمامًا - فهي تضيف بشكل أساسي حمل زائد واحد للمنشئ على PriorityQueue.

لقد كتبت بالفعل لماذا تمتص: انظر هذا المنشور .

لماذا لا يستطيع هؤلاء الأشخاص الذين تفكر فيهم أن يساهموا فقط في هذه المناقشة؟

إن وجود مراجعي واجهة برمجة التطبيقات يراقبون كل مشكلة من مشكلات واجهة برمجة التطبيقات ، أو حتى تلك المشاغبين فقط لا يتسع نطاقه بشكل جيد.

نعم ، كنت أسأل لماذا لا يستطيع مراجعو واجهة برمجة التطبيقات مراقبة كل مشكلة واحدة في واجهة برمجة التطبيقات. على وجه التحديد ، لقد استجبت بالفعل وفقًا لذلك.

من المنطقي؟

لا ، لقد سئمت من هذه المناقشة ، حقًا. من الواضح أن بعضًا منكم يا رفاق يشاركون فيها لمجرد أنها وظيفتك ويطلب منك ذلك. يحتاج البعض منكم إلى التوجيه طوال الوقت ، وهو أمر ممل للغاية. لقد طلبت مني حتى إثبات سبب تنفيذ قائمة انتظار ذات أولوية مع كومة داخلية ، ومن الواضح أنها تفتقر إلى خلفية علوم الكمبيوتر. لا يفهم البعض منكم ما هي الكومة حقًا ، مما يجعل المناقشة أكثر فوضوية.

انتقل مع PriorityQueue المعطل الذي لا يسمح بتحديث وإزالة العناصر. اتبع التصميم الخاص بك الذي لا يسمح بنهج OO الصحي. اتبع الحل الذي لا يسمح بإعادة استخدام المكتبة القياسية عند كتابة ملحق. اذهب إلى طريق جافا.

وهذا ... هذا مجرد أمر مذهل:

إذا ذهبنا مع Heap ، فإن العديد من المطورين ذوي المهارات المتوسطة سيرون (للأسف) "الكومة" ويخلطون بينها وبين كومة ذاكرة التطبيق (مثل الكومة والمكدس) بدلاً من "كومة بنية البيانات المعممة".

قدم API مع نهجك. انا فضولي.

لا أصدق أن هذا يحدث بالفعل ...

حسنًا ، excuuuuuuuuuuuuuuuusuuusuuuuuuuuuuuuuuuuuuuuuuus الولايات المتحدة الأمريكية لم تتح لي الفرصة للحصول على تعليم علوم الكمبيوتر المناسب لمعرفة أن Heap هو نوع من بنية البيانات تختلف عن كومة الذاكرة.

ومع ذلك ، فإن النقطة لا تزال قائمة. كومة من شيء ما لا تعني Heap . من ناحية أخرى ، يوضح PriorityQueue أنه يفعل ذلك بالضبط.

كتنفيذ مساند؟ بالتأكيد ، لا ينبغي أن تكون تفاصيل التنفيذ مصدر قلق لي.
بعض التجريد IHeap ؟ العظمى للمؤلفين API والناس الذين لديهم CS الرئيسية لمعرفة ما انها تستخدم ل، أي سبب لعدم لديهم.
إعطاء شيء ما اسمًا خفيًا لا يذكر القصد منه جيدًا وبالتالي يحد من قابلية الاكتشاف؟ 👎

حسنًا ، excuuuuuuuuuuuuuuusuuuuuusuuuuuuuuuuuuuuuuuuuuuuuus الولايات المتحدة الأمريكية لم تتح لي الفرصة للحصول على تعليم علوم الكمبيوتر المناسب لمعرفة أن Heap هو نوع من بنية البيانات مختلفة عن كومة الذاكرة.

هذا سخيف. في نفس الوقت تريد المشاركة في مناقشة بخصوص إضافة وظيفة من هذا القبيل. يبدو مثل التصيد.

كومة من شيء ما لا تعني شيئًا عن كونها مرتبة بطريقة ما.

انت مخطئ. يتم ترتيبها ، مثل كومة. كما في الصور التي ربطتها.

كتنفيذ مساند؟ بالتأكيد ، لا ينبغي أن تكون تفاصيل التنفيذ مصدر قلق لي.

لقد تناولت ذلك بالفعل. عائلة الأكوام ضخمة وهناك مستويان من التجريد فوق التنفيذ. قائمة انتظار الأولوية هي طبقة التجريد الثالثة.

إذا كنت بحاجة إلى مجموعة تسمح لي بتخزين كائنات الأشياء لمعالجتها ، حيث قد تحتاج بعض الحالات التي تأتي لاحقًا إلى المعالجة عاجلاً ، فلن أبحث عن شيء يسمى كومة. من ناحية أخرى ، تشير PriorityQueue إلى أنها تفعل ذلك تمامًا.

وبدون أي خلفية ، ستطلب من Google تزويدك بمقالات حول قوائم الانتظار ذات الأولوية؟ حسنًا ، يمكننا مناقشة ما هو أكثر أو أقل ترجيحًا في آرائنا. ولكن ، كما قيل بلطف شديد:

إذا كان لديك مدخلات ، فقدمها ، وادعمها بالبيانات والأدلة. قدم أدلة ضد رأي الآخرين إذا كنت لا توافق.

وبحسب المعطيات فأنت مخطئ:

استعلام | يضرب
: ----: |: ----: |
| "هيكل البيانات" "قائمة انتظار الأولوية" | 172000 |
| "بنية البيانات" "كومة" | 430،000 |

من المرجح تقريبًا أن تصادف كومة أثناء القراءة عن هياكل البيانات. بالإضافة إلى ذلك ، فهو اسم يعرفه مطورو Swift و Go و Rust و Python ، لأن مكتباتهم القياسية توفر بنية بيانات كهذه.

استعلام | يضرب
: ----: |: ----: |
| قائمة انتظار الأولوية "golang" | 3.390 |
| قائمة انتظار الأولوية "الصدأ" | 8.630 |
| "سريع" "قائمة انتظار الأولوية" | 18.600 |
| "قائمة انتظار الأفضلية بيثون" | 72.800 |
| "جولانج" "كومة" | 79.000 |
| "الصدأ" "كومة" | 492.000 |
| "سريع" "كومة" | 551.000 |
| "بيثون" "كومة" | 555.000 |

في الواقع ، إنه مشابه أيضًا لـ C ++ ، لأنه تم تقديم بنية بيانات كومة هناك في وقت ما في القرن الماضي.

إعطاء شيء ما اسمًا خفيًا لا يذكر القصد منه جيدًا وبالتالي يحد من قابلية الاكتشاف؟ 👎

لا توجد آراء. البيانات. أنظر فوق. خاصة لا توجد آراء من شخص ليس لديه خلفية. لن تكون Google في قائمة انتظار ذات أولوية دون الحصول على بعض القراءة حول هياكل البيانات من قبل. ويتم تغطية الكومة في العديد من هياكل البيانات 101 .

إنه الأساس في علوم الكمبيوتر. إنه أساسي. عندما يكون لديك عدة فصول دراسية من الخوارزميات وهياكل البيانات ، فإن الكومة هي شيء تراه في البداية.

لكن مازال:

  • أولاً - انظر إلى الأرقام أعلاه.
  • ثانيًا - فكر في جميع اللغات الأخرى حيث تكون الكومة جزءًا من المكتبة القياسية.

تحرير: راجع Google Trends .

بصفتي مطورًا آخر علميًا ذاتيًا ، ليس لدي مشكلة مع _heap_. بصفتي مطورًا يسعى دائمًا إلى التحسين ، فقد قضيت وقتًا في تعلم وفهم كل ما يتعلق بهياكل البيانات. باختصار ، أنا لا أتفق مع المعنى الضمني بأن اصطلاح التسمية يجب أن يستهدف أولئك الذين لم يأخذوا الوقت الكافي لفهم قاموس المجال الذي هم جزء منه.

كما أنني أعارض بشدة عبارات مثل " يجب أن يكون الاسم هو PriorityQueue". إذا كنت لا تريد مدخلات الناس ، فلا تجعلها مفتوحة المصدر ولا تطلبها.

اسمحوا لي أن أقدم بعض الشرح لكيفية تفكيرنا في تسمية واجهة برمجة التطبيقات:

  1. نميل إلى تفضيل التناسق داخل النظام الأساسي .NET فوق أي شيء آخر تقريبًا. هذا مهم لجعل واجهات برمجة التطبيقات تبدو مألوفة ومتوقعة. يعني هذا أحيانًا أننا نقبل أن الاسم ليس صحيحًا بنسبة 100٪ إذا كان هذا مصطلحًا استخدمناه من قبل.

  2. هدفنا هو تصميم منصة تكون في متناول مجموعة متنوعة من المطورين ، وبعضهم لم يتلق تعليمًا رسميًا في علوم الكمبيوتر. نعتقد أن جزءًا من سبب اعتبار .NET بشكل عام منتجًا للغاية وسهل الاستخدام يرجع جزئيًا إلى نقطة التصميم هذه.

نستخدم بشكل عام "اختبار محرك البحث" عندما يتعلق الأمر بفحص مدى شهرة وتأسيس الاسم أو المصطلحات. لذلك أنا أقدر بشدة البحث الذي أجرتهpgolebiowski . لم أقم بإجراء البحث بنفسي ولكني أشعر أن "الكومة" ليس مصطلحًا يبحث عنه العديد من الخبراء غير المتخصصين في المجال.

ومن ثم ، فإنني أميل إلى الاتفاق مع karelz على أن PriorityQueue يبدو الخيار الأفضل. فهو يجمع بين المفهوم الحالي (قائمة الانتظار) ويضيف الالتواء الذي يعبر عن القدرة المرغوبة: الاسترجاع المرتب بناءً على الأولوية. لكننا لسنا مرتبطين بهذا الاسم بشكل ثابت. غالبًا ما قمنا بتغيير أسماء هياكل البيانات والتقنيات بناءً على ملاحظات العملاء.

ومع ذلك ، أود أن أشير إلى أن هذا:

إذا كنت لا تريد مدخلات الناس ، فلا تجعلها مفتوحة المصدر ولا تطلبها.

هو انقسام خاطئ. لا يعني ذلك أننا لا نريد الحصول على تعليقات من نظامنا البيئي والمساهمين (من الواضح أننا نريد ذلك). ولكن في الوقت نفسه ، علينا أيضًا أن نقدر أن قاعدة عملائنا متنوعة تمامًا وأن المساهمين في GitHub (أو المطورين في فريقنا) ليسوا دائمًا أفضل وكيل لجميع عملائنا. سهولة الاستخدام صعبة ومن المحتمل أن يستغرق الأمر بعض التكرارات لإضافة مفاهيم جديدة إلى .NET ، خاصة في المجالات الشائعة للغاية مثل المجموعات.

pgolebiowski :

أنا أقدر عاليا رؤيتك وبياناتك واقتراحاتك. لكنني على الإطلاق لا أقدر أسلوبك في الجدال. لقد حصلت على شخصية تجاه كل من أعضاء فريقي وكذلك أعضاء المجتمع في هذا الموضوع. فقط لأنك لا تتفق معنا لا يمنحك الإذن لاتهامنا بعدم امتلاك الخبرة أو عدم الاهتمام لأنها "وظيفتنا فقط". ضع في اعتبارك أن الكثيرين منا قد انتقلوا فعليًا إلى جميع أنحاء العالم ، وتركوا عائلاتهم وأصدقائهم وراءنا فقط لأننا أردنا القيام بهذه المهمة. لا تعتبر التعليقات مثل تعليقاتك غير عادلة فحسب ، بل إنها لا تساعد أيضًا في تطوير التصميم.

لذلك بينما أحب أن أعتقد أن بشرتي سميكة ، ليس لدي الكثير من التسامح مع هذا النوع من السلوك. مجالنا معقد بالفعل بدرجة كافية ؛ لسنا بحاجة إلى إضافة اتصالات المواجهة والخصومة.

من فضلك كن محترما. إن انتقاد الأفكار بحماسة هو لعبة عادلة ، لكن مهاجمة الناس ليست كذلك. شكرا لك.

عزيزي كل من يشعر بالحزن ،

أعتذر عن خفض مستويات سعادتك من خلال موقفي القاسي.

تضمين التغريدة

لقد أخدعت نفسي هناك بارتكاب خطأ تقني. أعتذر. تم قبوله. بعد القيت لي في وقت لاحق. ليس بارد IMO.

أنا آسف لأن ما كتبته جعلك غير سعيد. على الرغم من أن الأمر لم يكن بهذا السوء كما وصفته - لقد قدمت هذا فقط كواحد من العديد من العوامل التي ساهمت في شعوري بالتعب . إنها أقل خطورة ، على ما أعتقد. لكن مع ذلك ، أنا آسف.

ونعم ، الجميع يرتكب أخطاء. هذا جيد. أنا أيضًا ، على سبيل المثال عن طريق الابتعاد أحيانًا.

أكثر ما جذبني هو "لقد طُلب منك فقط القيام بذلك ، أنت لا تصدق ذلك" - نعم ، هذا هو بالضبط سبب قيامي بذلك أيضًا في عطلات نهاية الأسبوع.

أنا آسف ، أستطيع أن أرى أنك تعمل بجد وأنا أقدر ذلك كثيرًا. لقد كان واضحًا لي كيف كنت مكرسًا بشكل خاص بحلول 5/10.

تضمين التغريدة

فقط لأنك لا تتفق معنا لا يمنحك الإذن لاتهامنا بعدم امتلاك الخبرة أو عدم الاهتمام لأنها "وظيفتنا فقط".

  • عدم وجود خبرة - موجه إلى الأشخاص الذين ليس لديهم خلفية في علوم الكمبيوتر ولا يفهمون مفهوم الأكوام / قوائم الانتظار ذات الأولوية. إذا كان هذا الوصف ينطبق على شخص ما - حسنًا ، فإنه ينطبق ، فهذا ليس خطأي.
  • عدم الاهتمام - موجه إلى أولئك الذين يميلون إلى تجاهل بعض النقاط الفنية ، وبالتالي فرض الحجج المتكررة ، مثل جعل المناقشة فوضوية ويصعب متابعتها من قبل الآخرين (وهذا بدوره يؤدي إلى مساهمة أقل).

التعليقات مثل تعليقاتك غير عادلة للغاية ولا تساعد أيضًا في تطوير التصميم.

  • كانت تعليقاتي القاسية نتيجة لعدم كفاءة هذه المناقشة. عدم الكفاءة = طريقة فوضوية للمناقشة ، حيث لا تتم معالجة / حل النقاط ونتحرك أبعد على الرغم من ذلك => ممل.
  • أيضًا ، كواحد من الدوافع الرئيسية في المناقشة ، أشعر بقوة أنني فعلت الكثير للمساعدة في تطوير التصميم. من فضلك لا تشيطنني ، كما تحاول أن تفعل هنا وفي وسائل التواصل الاجتماعي.

إذا كان هناك أي شخص مريض يريد أن "يلعقني" ، فلا تتردد.


لقد واجهنا مشكلة وتعاملنا معها. لقد تعلم الجميع شيئًا منه. أستطيع أن أرى أن الجميع هنا مهتمون بجودة إطار العمل ، وهو أمر رائع للغاية ويجعلني متحمسًا للمساهمة. إنني أتطلع إلى مواصلة العمل على CoreFX معك. ومع ذلك ، سوف أتطرق إلى مدخلاتك الفنية الجديدة على الأرجح غدًا.

تضمين التغريدة

نأمل أن نلتقي وجهًا لوجه في وقت ما. أعتقد بصدق أن جزءًا من التحدي المتمثل في القيام بكل شيء عبر الإنترنت هو أن الشخصيات يمكن أن تختلط أحيانًا بطرق سيئة دون نية من أي جانب.

إنني أتطلع إلى مواصلة العمل على CoreFX معك. ومع ذلك ، سوف أتطرق إلى مدخلاتك الفنية الجديدة على الأرجح غدًا.

كذلك هنا. هذه مساحة مثيرة للاهتمام وهناك الكثير من الأشياء المدهشة التي يمكننا القيام بها معًا :-)

pgolebiowski أولاً ، شكرًا على ردك. إنه يظهر اهتمامك وأنت تقصد جيدًا (وهو شيء أتمنى سرًا أن يفعله كل شخص / مطور في العالم ، كل النزاعات هي مجرد سوء فهم / سوء فهم). وهذا يجعلني سعيدًا حقًا - إنه يجعلني مستمراً ومتحمساً.
أود أن أقترح أن نبدأ من جديد في علاقتنا. دعنا نعيد المناقشة إلى التقنية ، دعنا نتعلم جميعًا من هذا الموضوع ، كيفية التعامل مع مواقف مماثلة في المستقبل ، دعنا نفترض مرة أخرى أن الطرف الآخر لديه مصلحة فقط في النظام الأساسي.
راجع للشغل: هذه واحدة من عدد قليل من المواجهات / المناقشات الصعبة على CoreFX repo في الأشهر التسعة الماضية ، وكما ترى ، ما زلنا (بما في ذلك / أنا) نتعلم كيفية التعامل معها بشكل جيد - لذلك هذا المثال بالتحديد مستمر حتى تعود بالفائدة علينا وستجعلنا أفضل في المستقبل وتساعدنا على فهم وجهات النظر المختلفة بشكل أفضل من أعضاء المجتمع المتحمسين. ربما ستشكل تحديثاتنا لمستندات المساهمة ...

كانت تعليقاتي القاسية نتيجة لعدم كفاءة هذه المناقشة. عدم الكفاءة = طريقة فوضوية للمناقشة ، حيث لا تتم معالجة / حل النقاط ونتحرك أبعد على الرغم من ذلك => ممل.

فهمت إحباطك! ومن المثير للاهتمام أن إحباطًا مشابهًا كان أيضًا على الجانب الآخر لنفس السبب 😉 ... من المضحك تقريبًا كيف يعمل العالم :).
لسوء الحظ ، فإن المناقشة الصعبة هي جزء من الوظيفة عندما تقود قرار التصميم. إنه الكثير من العمل. كثير من الناس يقللون من شأنها. المهارة الأساسية المطلوبة هي الصبر مع الجميع والقدرة على تجاوز رأيك الخاص والتفكير في كيفية تحقيق الإجماع حتى لو لم ينسجم معك. لهذا السبب اقترحت تقديم اقتراحين و "تصعيد" المناقشة الفنية لمجموعة مراجعي واجهة برمجة التطبيقات (بشكل أساسي لأنني لا أعرف على وجه اليقين أنني على صواب ، على الرغم من أنني آمل سرًا أن أكون على صواب مثل أي مطور آخر في العالم 😉 ).

من الصعب جدًا أن يكون لديك رأي حول موضوع ما وأن تدفع المناقشة إلى توافق الآراء حول نفس الموضوع. من هذا المنظور ، أنت وأنا لدينا الأكثر شيوعًا في هذا الموضوع - لدينا آراء ، لكننا نحاول دفع المناقشة إلى الختام واتخاذ القرار. لذلك دعونا نعمل معا بشكل وثيق.

أسلوبي العام هو: عندما أعتقد أن شخصًا ما يهاجمني ، يكون ذلك شريرًا أو كسولًا أو يحبطني أو شيء من هذا القبيل. أسأل نفسي أولاً وكذلك الشخص المعين: لماذا؟ لما قلت ذلك؟ ماذا تقصد؟
عادة ما تكون علامة على عدم فهم / توصيل الدوافع. أو قم بالإشارة إلى كثرة القراءة بين السطور ورؤية الشتائم / الاتهامات / النوايا السيئة حيث لا تكون كذلك.


الآن بما أنني لست خائفًا من الاستمرار في مناقشة الأسئلة الفنية ، فإليك ما أردت طرحه مسبقًا:

انتقل مع PriorityQueue المعطل الذي لا يسمح بتحديث وإزالة العناصر.

هذا شيء لا أفهمه. إذا تركنا IHeap (في اقتراحي / اقتراحي الأصلي هنا) ، فلماذا لا يكون ذلك ممكنًا؟
لا يوجد اختلاف في IMO بين المقترحين من وجهة نظر قدرات الفئة ، والفرق الوحيد هو - هل نضيف زيادة التحميل على المُنشئ PriorityQueue(IHeap) أم لا (مع ترك الخلاف حول اسم الفئة جانبًا كمشكلة مستقلة يجب حلها) .

إخلاء المسؤولية (لتجنب سوء الفهم): ليس لدي وقت لقراءة المقالات وإجراء البحوث ، وأتوقع إجابة مختصرة ، وتقديم حجة منقطعة النظير من أي شخص يريد قيادة المناقشة الفنية. ملاحظة: ليس أنا التصيد. أود أن أطرح نفس السؤال على أي شخص في فريقنا يدعي هذا. إذا لم تكن لديك الطاقة لشرح ذلك / قيادة المناقشة (وهو أمر يمكن فهمه تمامًا نظرًا للصعوبات واستثمار الوقت من جانبك) ، فقط قل ذلك ، فلا توجد مشاعر قاسية. من فضلك لا تشعر بالضغط من قبلي أو من أي شخص (وهذا ينطبق على كل شخص في الموضوع).

لا تحاول إضافة تعليق آخر غير ضروري هنا ، فهذا الموضوع كان طويلاً للغاية. القاعدة رقم 1 في عصر الإنترنت هي تجنب الاتصالات النصية إذا كنت تهتم بعلاقة الناس. (حسنًا ، لقد صاغتها). أعتقد أن بعض المجتمعات الأخرى مفتوحة المصدر ستتحول إلى Google Hangout لهذا النوع من المناقشة إذا كانت الحاجة واضحة. عندما تنظر إلى وجوه الآخرين ، لن تقول أبدًا أي شيء "مهين" ، ويتعرف الناس على بعضهم البعض بسرعة كبيرة. ربما يمكننا المحاولة كذلك؟

karelz نظرًا لطول المناقشة أعلاه ، فمن المستبعد جدًا أن يساهم أي شخص جديد إذا لم يتم تعديل التدفق. على هذا النحو ، أود أن أقترح النهج التالي الآن:

  • سأقوم بالتصويت على الجوانب الأساسية ، الواحد تلو الآخر. سيكون لدينا مدخلات واضحة من المجتمع. من الناحية المثالية ، سيأتي مراجعو واجهة برمجة التطبيقات إلى هنا أيضًا للتعليق قريبًا.
  • ستحتوي "مشاركات التصويت" على معلومات كافية لتتمكن من تجاهل الجدار الكامل للنص أعلاه.
  • بعد الانتهاء من جلسة التصويت هذه ، سنعرف ما يمكن توقعه من مراجعي واجهة برمجة التطبيقات وسنكون قادرين على المضي قدمًا في نهج معين. عندما نتفق على الجوانب الأساسية ، سيتم إغلاق هذه القضية ، وفتح واحدة أخرى (والتي ستشير إلى هذا). في الإصدار الجديد ، سألخص استنتاجاتنا وأقدم اقتراح API الذي يعكس هذه القرارات. وسوف نأخذها من هناك.

هل هناك فرصة لفهم ذلك؟

PriorityQueue الذي لا يسمح بتحديث وإزالة العناصر.

كان يتعلق بالمقترح الأصلي الذي يفتقر إلى هذه القدرات :) آسف لعدم توضيح الأمر.

إذا لم تكن لديك الطاقة لشرح ذلك / قيادة المناقشة (وهو أمر يمكن فهمه تمامًا نظرًا للصعوبات واستثمار الوقت من جانبك) ، فقط قل ذلك ، فلا توجد مشاعر قاسية. من فضلك لا تشعر بالضغط من قبلي أو من أي شخص (وهذا ينطبق على كل شخص في الموضوع).

لن استسلم. لا ألم ولا ربح xD

ههههههههههه

أعتقد أن بعض المجتمعات الأخرى مفتوحة المصدر ستتحول إلى Google Hangout لهذا النوع من المناقشة إذا كانت الحاجة واضحة. ربما يمكننا المحاولة كذلك؟

تبدو جيدا ؛)

توفير واجهة

لا يهم إذا استخدمنا Heap / IHeap أو PriorityQueue / IPriorityQueue (أو أي شيء آخر) ، للوظيفة التي نحن على وشك توفيرها ...

_ هل ترغب في الحصول على واجهة مع التنفيذ؟ _

ل

تضمين التغريدة

من خلال دعم هذا التنفيذ من خلال واجهة ، يمكن لمن يهتمون بالتطبيقات الأخرى التبديل بسهولة في تطبيق آخر (من إنشاءنا أو في مكتبة خارجية) ويظل متوافقًا مع الكود الذي يقبل الواجهة.

يمكن لأولئك الذين لا يهتمون بها أن يتجاهلوها ويذهبوا مباشرة إلى التنفيذ الملموس. أولئك الذين يهتمون يمكنهم استخدام أي تنفيذ من اختيارهم.

ضد

تضمين التغريدة

هناك العديد من الخوارزميات المختلفة التي يمكن استخدامها نظريًا لتنفيذ بنية البيانات هذه. ومع ذلك ، يبدو من غير المحتمل أن يتم شحن إطار العمل بأكثر من واحد ، وفكرة أن كتاب المكتبات يحتاجون حقًا إلى واجهة متعارف عليها بحيث يمكن للأطراف المختلفة التنسيق في كتابة تطبيقات كومة متوافقة متقاطعة لا يبدو ذلك مرجحًا.

علاوة على ذلك ، بمجرد إصدار الواجهة ، لا يمكن تغييرها أبدًا بسبب التوافق. هذا يعني أن الواجهة تميل إلى التخلف عن الفئات الملموسة من حيث الوظائف (مشكلة في IList و IDictionary اليوم).

تضمين التغريدة

الواجهة هي سيناريو خبير متقدم للغاية.

إذا أصبحت المكتبة وواجهة IHeap شائعة حقًا ، فيمكننا تغيير رأينا لاحقًا وإضافة IHeap بناءً على الطلب (عبر التحميل الزائد للمُنشئ) ، لكنني لا أعتقد أنه مفيد / متوافق مع ما تبقى من BCL يكفي لتبرير تعقيد إضافة واجهة جديدة الآن. ابدأ بسيطًا ، وتوسع إلى معقد فقط إذا لزم الأمر.

تأثير القرار المحتمل

  • تضمين الواجهة يعني أنه لا يمكننا تغييرها في المستقبل.
  • يعني عدم تضمين الواجهة أن الأشخاص يكتبون رمزًا يستخدم إما حل المكتبة القياسي الخاص بنا أو مكتوبًا مقابل حل تقدمه مكتبة تابعة لجهة خارجية (لا توجد واجهة مشتركة من شأنها تمكين التوافق المتبادل).

استخدم 👍 و للتصويت على هذا (مع واجهة ، على التوالي). بدلا من ذلك ، اكتب تعليق. من الناحية المثالية ، سيشارك مراجعو واجهة برمجة التطبيقات.

أود أن أضيف أنه في حين أن تغيير الواجهات صعب ، مع طرق الامتداد (وخصائص قادمة) يسهل توسيع الواجهات و / أو العمل معها (انظر LINQ)

أود أن أضيف أنه في حين أن تغيير الواجهات صعب ، مع طرق الامتداد (وخصائص قادمة) يسهل توسيع الواجهات و / أو العمل معها (انظر LINQ)

يمكنهم العمل فقط مع الطرق المحددة علنًا على الواجهة ؛ لذا فهذا يعني الحصول عليها بالشكل الصحيح من المرة الأولى.

أقترح التوقف عن استخدام الواجهة قليلاً حتى يتم استخدام الفصل واستقراره - ثم تقديم واجهة. (مع كون الجدل حول شكل الواجهة قضية منفصلة)

لأكون صريحًا ، الشيء الوحيد الذي يهمني هو الواجهة. سيكون التنفيذ القوي أمرًا رائعًا ، لكنني (أو أي شخص آخر) يمكنني دائمًا إنشاء تطبيق خاص بي.

أتذكر منذ بضع سنوات كيف أجرينا نفس المحادثة بالضبط مع HashSet<T> . أرادت Microsoft HashSet<T> بينما أراد المجتمع ISet<T> . إذا كنت أتذكر بشكل صحيح ، فقد حصلنا على HashSet<T> أولاً و ISet<T> الثانية. بدون واجهة ، كان استخدام HashSet<T> محدودًا تمامًا لأنه من الصعب (إن لم يكن من المستحيل غالبًا) تغيير واجهة برمجة تطبيقات عامة.

يجب أن أشير إلى أن هناك أيضًا SortedSet<T> الآن ، ناهيك عن العديد من التطبيقات بخلاف BCL ISet<T> . لقد استخدمت ISet<T> في واجهات برمجة التطبيقات العامة وأنا ممتن لذلك. يمكن أن يستخدم تطبيقي الخاص أي تطبيق ملموس أشعر أنه صحيح. يمكنني أيضًا استبدال تطبيق بآخر بسهولة دون كسر أي شيء. لن يكون هذا ممكنًا بدون الواجهة.

بالنسبة لأولئك الذين يقولون أنه يمكننا دائمًا تحديد واجهاتنا الخاصة ، ضع في اعتبارك هذا. افترض للحظة أن ISet<T> في BCL لم يحدث أبدًا. يمكنني الآن إنشاء واجهتي الخاصة IMySet<T> بالإضافة إلى تطبيقات قوية. ومع ذلك ، في يوم من الأيام يتم تحرير BCL HashSet<T> . قد تنفذ أو لا تنفذ ISet<T> ، لكنها لا تطبق IMySet<T> . نتيجة لذلك ، لا يمكنني المبادلة بـ HashSet<T> كتطبيق لـ IMySet<T> .

أخشى أننا سنكرر هذه المهزلة مرة أخرى.
إذا لم تكن مستعدًا للالتزام بواجهة ، فمن السابق لأوانه تقديم فصل دراسي محدد.

أجد التباين في الآراء مهمًا. بمجرد النظر إلى الأرقام ، يتجه عدد أكبر قليلاً من الأشخاص للواجهة ، لكن هذا لا يخبرنا كثيرًا. سأحاول سؤال بعض الأشخاص الآخرين الذين شاركوا في المناقشة من قبل ولكنهم لم يعبروا عن آرائهم فيما يتعلق بالواجهة حتى الآن:

ebicklesvickhoraciojcfilhopaulcbettsjustinvpakoeplingermbeidlerSirCmpwnandrewgmorrisweshaggardBrannonKingNKnuspererdanmosemsftianhayssafernVisualMelon @ Joe4evrjcouv @ xied75

بالنسبة إلى الأشخاص الذين تم إخطارهم: _guys ، هل يمكنك تقديم مدخلاتك؟ سيكون ذلك مفيدًا للغاية ، حتى إذا قمت بالتصويت بـ: +1 :،: -1 :. يمكنك البدء في قراءة تعليق المشكلة (4 منشورات أعلاه هنا) - نحن نناقش ما إذا كان توفير واجهة فكرة جيدة (هذا الجانب فقط في الوقت الحالي ، حتى يتم حله).

ربما يكون بعض هؤلاء الأشخاص مراجعين لواجهة برمجة التطبيقات ، لكنني أعتقد أننا بحاجة إلى دعمهم في هذا القرار الأساسي قبل المضي قدمًا. karelz ، terrajobst ، هل من الممكن أن تطلب منهم مساعدتنا في حل هذا الجانب؟ ستكون مدخلاتهم قيّمة للغاية ، لأنهم هم الذين سيراجعونها في النهاية - سيكون من المفيد جدًا معرفة ذلك في هذه المرحلة ، قبل الالتزام بنهج معين (أو الذهاب مع أكثر من اقتراح واحد ، والذي سيكون أمرًا مملًا وقليلًا من العبث ، حيث يمكننا معرفة قرارهم مسبقًا).

أنا شخصياً أؤيد الواجهة ، ولكن إذا كان القرار مختلفًا ، فأنا سعيد باتباع مسار مختلف.

لا أرغب في جر مراجعي واجهة برمجة التطبيقات إلى المناقشة - إنها طويلة وفوضوية ، ولن يكون من المفيد لمراجعي واجهة برمجة التطبيقات إعادة قراءة كل شيء أو حتى مجرد تحديد آخر رد مهم (أفقد نفسي فيه ).
أعتقد أننا وصلنا إلى النقطة التي يمكننا فيها إنشاء مقترحين رسميين لواجهة برمجة التطبيقات (انظر المثال "الجيد" هناك) وتسليط الضوء على إيجابيات / سلبيات كل منهما. يمكننا بعد ذلك مراجعتها في اجتماع مراجعة API وتقديم توصية ، مع أخذ الأصوات في الاعتبار. اعتمادًا على المناقشة هناك (إذا كانت هناك آراء متعددة) ، فقد نعود ونبدأ استطلاع Twitter / تصويت GH إضافي ، إلخ.

راجع للشغل: اجتماعات مراجعة API تحدث تقريبًا كل ثلاثاء.

للمساعدة في البدء ، إليك الشكل الذي يجب أن يبدو عليه الاقتراح:

مثال الاقتراح / البذور

ج #
System.Collections.Generic. مساحة الاسم
{
فئة عامة أولوية
: I لا يحصى، ICollection، IEnumerable، IReadOnlyCollection
{
public PriorityQueue ()؛
public PriorityQueue (سعة int) ؛
public PriorityQueue (IComparerالمقارنة) ؛
public PriorityQueue (IEnumerableمجموعة)؛
public PriorityQueue (IEnumerableجمع ، IComparerالمقارنة) ؛
public PriorityQueue (int السعة ، IComparerالمقارنة) ؛

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

}
""

لكى يفعل:

  • مثال على الاستخدام مفقود (من غير الواضح بالنسبة لي كيف أعبر عن أولوية العناصر) - ربما يجب علينا إعادة تصميمه ليكون "int" كمدخل قيمة ذات أولوية؟ ربما بعض التجريد في ذلك؟ (أعتقد أنه تمت مناقشته أعلاه ، ولكن الخيط طويل جدًا للقراءة ، ولهذا السبب نحتاج إلى مقترحات ملموسة وليس المزيد من المناقشة)
  • سيناريو UpdatePriority مفقود
  • أي شيء آخر نوقش أعلاه مفقود هنا؟

@ Karelz حسنًا ، سأفعل ذلك ، ترقبوا! :ابتسامة:

على ما يرام. بقدر ما أستطيع أن أقول ، لن تمر واجهة أو كومة من مراجعة API. على هذا النحو ، أود أن أقترح حلاً مختلفًا قليلاً ، متناسيًا الكومة الرباعية على سبيل المثال. تختلف بنية البيانات أدناه من نواحٍ قليلة عما يمكن أن نجده في Python و Java و C ++ و Go و Swift و Rust (على الأقل هؤلاء).

ينصب التركيز الرئيسي على الصحة والاكتمال من حيث الوظيفة والحدس ، مع الحفاظ على التعقيدات المثلى والأداء الرائع في العالم الحقيقي.

تضمين التغريدة

عرض

المنطق

إنها حالة شائعة جدًا أن المستخدم لديه مجموعة من العناصر ، حيث يكون لبعضها أولوية أعلى من البعض الآخر. في النهاية ، يريدون الاحتفاظ بهذه المجموعة من العناصر بترتيب معين حتى يتمكنوا من أداء العمليات التالية بكفاءة:

  1. أضف عنصرًا جديدًا إلى المجموعة.
  2. استرجع العنصر بأولوية قصوى (وكن قادرًا على إزالته).
  3. إزالة عنصر من المجموعة.
  4. تعديل عنصر في المجموعة.
  5. دمج مجموعتين.

إستعمال

قائمة المصطلحات

  • القيمة - بيانات المستخدم.
  • مفتاح - كائن يُستخدم لأغراض الطلب.

أنواع مختلفة من بيانات المستخدم

أولاً سنركز على بناء قائمة انتظار الأولوية (فقط إضافة العناصر). تعتمد الطريقة التي يتم بها على نوع بيانات المستخدم.

السيناريو 1

  • TKey و TValue كائنان منفصلان.
  • TKey قابل للمقارنة.
var queue = new PriorityQueue<int, string>();

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

السيناريو 2

  • TKey و TValue كائنان منفصلان.
  • TKey غير قابل للمقارنة.
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");

السيناريو 3

  • TKey موجود في TValue .
  • TKey غير قابل للمقارنة.
public class MyClass
{
    public MyKey Key { get; set; }
}

بصرف النظر عن أداة المقارنة الرئيسية ، نحتاج أيضًا إلى محدد رئيسي:

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 */ ));
ملحوظات
  • نستخدم طريقة Enqueue هنا. هذه المرة يستغرق الأمر وسيطة واحدة فقط ( TValue ).
  • إذا تم تحديد محدد المفتاح ، يجب أن تقوم الطريقة Enqueue(TKey, TValue) بإخراج InvalidOperationException .
  • إذا لم يتم تعريف محدد المفتاح ، يجب أن تقوم الطريقة Enqueue(TValue) بإخراج InvalidOperationException .

السيناريو 4

  • TKey موجود في TValue .
  • TKey قابل للمقارنة.
var queue = new PriorityQueue<MyKey, MyClass>(selector);

queue.Enqueue(new MyClass( /* args */ ));
queue.Enqueue(new MyClass( /* args */ ));
queue.Enqueue(new MyClass( /* args */ ));
ملحوظات
  • يُفترض أن المقارنة لـ TKey هي Comparer<TKey>.Default ، كما في السيناريو 1 .

السيناريو 5

  • TKey و TValue كائنات منفصلة ، ولكن من نفس النوع.
  • TKey قابل للمقارنة.
var queue = new PriorityQueue<int, int>();

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

السيناريو 6

  • بيانات المستخدم هي كائن واحد ، وهو قابل للمقارنة.
  • لا يوجد مفتاح مادي أو لا يرغب المستخدم في استخدامه.
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 */ ));
ملحوظات

في البداية هناك غموض.

  • من الممكن أن يكون MyClass كائنًا منفصلاً ويريد المستخدم ببساطة أن يكون المفتاح والقيمة منفصلين ، كما كان الحال في السيناريو 5 .
  • ومع ذلك ، قد يكون أيضًا كائنًا واحدًا (كما في هذه الحالة).

إليك كيف يتعامل PriorityQueue مع الغموض:

  • إذا تم تحديد مفتاح محدد ، فلا يوجد غموض. مسموح فقط بـ Enqueue(TValue) . لذلك ، فإن الحل البديل للسيناريو 6 هو ببساطة تحديد المحدد وتمريره إلى المنشئ.
  • إذا لم يتم تعريف محدد المفتاح ، فسيتم حل الغموض باستخدام أول طريقة Enqueue :

    • إذا تم استدعاء Enqueue(TKey, TValue) في المرة الأولى ، فسيتم اعتبار المفتاح والقيمة كائنين منفصلين ( السيناريو 5 ). من الآن فصاعدًا ، يجب أن تقدم طريقة Enqueue(TValue) InvalidOperationException .

    • إذا تم استدعاء Enqueue(TValue) في المرة الأولى ، فسيتم اعتبار المفتاح والقيمة على أنهما نفس الكائن ، فيتم استنتاج محدد المفتاح ( السيناريو 6 ). من الآن فصاعدًا ، يجب أن تؤدي طريقة Enqueue(TKey, TValue) InvalidOperationException .

وظائف أخرى

لقد قمنا بالفعل بتغطية إنشاء قائمة انتظار ذات أولوية. نحن نعرف أيضًا كيفية إضافة عناصر إلى المجموعة. الآن سوف نركز على الوظائف المتبقية.

أعلى عنصر ذي أولوية

هناك عمليتان يمكننا القيام بهما على العنصر ذي الأولوية القصوى - استعادته أو إزالته.

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

ما يتم إرجاعه بالضبط بواسطة الطرق Peek و Dequeue ستتم مناقشته لاحقًا.

تعديل عنصر

بالنسبة للعنصر التعسفي ، قد نرغب في تعديله. على سبيل المثال ، إذا تم استخدام قائمة انتظار ذات أولوية خلال عمر الخدمة بالكامل ، فهناك احتمال كبير أن يرغب المطور في الحصول على إمكانية تحديث الأولويات.

من المثير للدهشة أن مثل هذه الوظيفة لا يتم توفيرها من خلال هياكل البيانات المكافئة: في Python و Java و C ++ و Go و Swift و Rust . ربما البعض الآخر أيضًا ، لكني قمت بفحصها فقط. النتيجة؟ المطورين المحبطين:

لدينا خياران أساسيان هنا:

  • افعل ذلك بطريقة Java ولا تقدم هذه الوظيفة. أنا ضد ذلك بشدة. إنه يفرض على المستخدم إزالة عنصر من المجموعة (وهذا وحده لا يعمل بشكل جيد ، لكنني سأصل إليه لاحقًا) ثم إضافته مرة أخرى. إنه قبيح للغاية ، لا يعمل في جميع الحالات ، وغير فعال.
  • قدم مفهوم جديد للمقابض .

مقابض

في كل مرة يضيف فيها المستخدم عنصرًا إلى قائمة انتظار الأولوية ، يتم منحه مؤشرًا:

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

المقبض عبارة عن فئة بها واجهة برمجة التطبيقات العامة التالية:

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

إنها إشارة إلى عنصر فريد في قائمة انتظار الأولوية. إذا كنت مهتمًا بالكفاءة ، فيرجى الاطلاع على الأسئلة الشائعة (ولن تشعر بالقلق بعد الآن).

يتيح لنا هذا النهج تعديل عنصر فريد بسهولة في قائمة انتظار الأولوية ، بطريقة بديهية وبسيطة للغاية:

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

في المثال أعلاه ، نظرًا لوجود محدد محدد محدد ، يمكن للمستخدم تحديث الكائن بنفسه. تحتاج قائمة انتظار الأولوية ببساطة إلى الإخطار لإعادة ترتيب نفسها (لا نريد أن نجعلها قابلة للملاحظة).

على غرار كيفية تأثير نوع بيانات المستخدم على طريقة إنشاء قائمة انتظار ذات أولوية وتعبئتها بالعناصر ، تختلف طريقة تحديثها أيضًا. سأجعل السيناريوهات أقصر هذه المرة ، لأنك ربما تعرف بالفعل ما سأكتبه.

سيناريوهات

السيناريو 7
  • TKey و TValue كائنان منفصلان.
var queue = new PriorityQueue<int, string>();

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

queue.Update(handle, 3);

كما ترى ، يوفر هذا النهج طريقة بسيطة للإشارة إلى عنصر فريد في قائمة انتظار الأولوية ، دون طرح أي أسئلة. إنه مفيد بشكل خاص في السيناريوهات التي قد تتكرر فيها المفاتيح. أيضًا ، هذا فعال للغاية - نحن نعرف العنصر الذي يجب تحديثه في O (1) ، ويمكن إجراء العملية في O (log n).

بدلاً من ذلك ، يمكن للمستخدم استخدام طرق إضافية تبحث في الهيكل بأكمله في O (n) ثم تحديث العنصر الأول الذي يطابق الوسيطات. هذه هي الطريقة التي تتم بها إزالة عنصر في Java. إنه ليس صحيحًا ولا فعالًا تمامًا ، ولكنه في بعض الأحيان أبسط:

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

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

queue.Update("three", 30);

الطريقة أعلاه تعثر على العنصر الأول بقيمة تساوي "three" . سيتم تحديث مفتاحه إلى 30 . لا نعرف على الرغم من أيهما سيتم تحديثه إذا كان هناك أكثر من واحد يلبي الشرط.

يمكن أن يكون أكثر أمانًا باستخدام طريقة Update(TKey oldKey, TValue, TKey newKey) . هذا يضيف شرطًا آخر - يجب أن يتطابق المفتاح القديم أيضًا. كلا الحلين أبسط ، لكنهما ليسا آمنين بنسبة 100٪ وأقل أداءً (O (1 + log n) مقابل O (n + log n)).

السيناريو 8
  • TKey موجود في TValue .
var queue = new PriorityQueue<int, MyClass>(selector);

/* adding some elements */

queue.Update(handle);

هذا السيناريو هو ما تم تقديمه كمثال في قسم المقابض .

يتم تحقيق ما ورد أعلاه في O (تسجيل الدخول). بدلاً من ذلك ، يمكن للمستخدم استخدام طريقة Update(TValue) تجد العنصر الأول مساويًا للعنصر المحدد وتقوم بإعادة الترتيب الداخلي. بالطبع ، هذا هو O (n).

السيناريو 9
  • TKey و TValue كائنات منفصلة ، ولكن من نفس النوع.

باستخدام المقبض ، لا يوجد غموض ، كما هو الحال دائمًا. في حالة وجود طرق أخرى تسمح بالتحديث - هناك بالطبع. هذه مفاضلة بين البساطة والأداء والصحة (والتي تعتمد على ما إذا كان يمكن تكرار البيانات أم لا).

السيناريو 10
  • بيانات المستخدم هي كائن واحد.

مع مقبض ، لا توجد مشكلة مرة أخرى. قد يكون التحديث عبر طرق أخرى أبسط - ولكن مرة أخرى ، أقل أداءً وليس صحيحًا دائمًا (إدخالات متساوية).

إزالة عنصر

يمكن القيام به ببساطة وبشكل صحيح من خلال مقبض. بدلاً من ذلك ، عبر الطرق Remove(TValue) و Remove(TKey, TValue) . نفس القضايا كما هو موضح سابقا.

دمج مجموعتين

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

/* add some elements to both */

queue1.Merge(queue2);

ملحوظات

  • بعد الانتهاء من الدمج ، تشترك قوائم الانتظار في نفس التمثيل الداخلي. يمكن للمستخدم استخدام أي من الاثنين.
  • يجب أن تتطابق الأنواع (محددة بشكل ثابت).
  • المقارنات يجب أن تكون متساوية ، وإلا InvalidOperationException .
  • يجب أن تكون المحددات متساوية ، وإلا InvalidOperationException .

API المقترحة

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

أسئلة مفتوحة

المسند بدلا من ذلك

أنا أؤيد بشدة نهج المقابض ، لأنه فعال وبديهي وصحيح تمامًا . السؤال هو كيف تتعامل مع طرق أبسط ولكن من المحتمل ألا تكون آمنة. شيء واحد يمكننا القيام به هو استبدال تلك الأساليب الأبسط بشيء مثل هذا:

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

سيكون الاستخدام رائعًا وقويًا. وبديهية حقًا. ومقروء (معبر جدا). أنا لهذا الشخص.

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

من المحتمل أن يكون تعيين المفتاح الجديد وظيفة أيضًا ، والتي تأخذ المفتاح القديم وتحوله بطريقة ما.

نفس الشيء بالنسبة لـ Contains و Remove .

مفتاح التحديث

إذا لم يتم تعريف محدد المفتاح (وبالتالي يتم الاحتفاظ بالمفتاح والقيمة بشكل منفصل) ، يمكن تسمية الطريقة UpdateKey . ربما يكون أكثر قابلية للتعبير. عندما يتم تحديد مفتاح التحديد ، يكون Update أفضل ، لأن المفتاح تم تحديثه بالفعل وما يلزم القيام به هو إعادة ترتيب بعض العناصر ضمن قائمة انتظار الأولوية.

التعليمات

أليست المقابض غير فعالة؟

لا توجد مشكلة في الكفاءة فيما يتعلق باستخدام المقابض. الخوف الشائع هو أنه سيتطلب تخصيصات إضافية ، لأننا نستخدم كومة تستند إلى مصفوفة داخليًا. لا تخافوا. واصل القراءة.

كيف ستنفذها بعد ذلك؟

سيكون نهجًا مختلفًا تمامًا لتقديم قائمة انتظار ذات أولوية. لأول مرة ، لن توفر المكتبة القياسية هذه الوظيفة التي يتم تنفيذها ككومة ثنائية ، والتي يتم تمثيلها كمصفوفة تحتها. سيتم تنفيذه باستخدام كومة اقتران ، والتي لا تستخدم مصفوفة حسب التصميم - إنها تمثل شجرة بدلاً من ذلك.

إلى أي مدى سيكون الأداء؟

  • بالنسبة للإدخال العشوائي العام ، سيكون الكومة الرباعية أسرع قليلاً.
  • ومع ذلك ، يجب أن يعتمد على مصفوفة ليكون أسرع. بعد ذلك ، لا يمكن أن تستند المقابض إلى العقد ببساطة - ستكون هناك حاجة إلى تخصيصات إضافية. ثم - لم نتمكن من تحديث وإزالة العناصر بشكل معقول.
  • بالطريقة التي تم تصميمها بها الآن ، لدينا واجهة برمجة تطبيقات سهلة الاستخدام مع تنفيذ فعال للغاية.
  • فائدة إضافية لوجود كومة ازدواج تحتها هي القدرة على دمج قائمتين من قوائم الانتظار ذات الأولوية في O (1) ، بدلاً من O (n) كما هو الحال في أكوام ثنائية / رباعية.
  • كومة المزاوجة لا تزال سريعة للغاية. انظر المراجع. في بعض الأحيان يكون أسرع من الأكوام الرباعية (يعتمد على بيانات الإدخال والعمليات المنفذة ، وليس فقط الدمج).

مراجع

  • فريدمان ، روبرت سيدجويك ، دانيال سليتور ، روبرت إي تارجان (1986) ، كومة الاقتران: شكل جديد من الكومة ذاتية الضبط ، الخوارزمية 1: 111-129 .
  • دانيال إتش لاركن ، سيدهارتا سين ، روبرت إي تارجان (2014) ، دراسة تجريبية من العودة إلى الأساسيات لقوائم الانتظار ذات الأولوية ، arXiv: 1403.0252v1 [cs.DS].

راجع للشغل ، إنه لتكرار مراجعة API الأول. يجب حل القسم الذي يحتوي على _الأسئلة المفتوحة_ (أحتاج إلى رأيك [ومراجعي واجهة برمجة التطبيقات إن أمكن]). إذا مر التكرار الأول جزئيًا على الأقل ، فبالنسبة لبقية المناقشة أرغب في إنشاء عدد جديد (أغلق وقم بالإشارة إلى هذه المشكلة).

بقدر ما أستطيع أن أقول ، لن تمر واجهة أو كومة من مراجعة API.

pgolebiowski : لماذا لا تغلق القضية بعد ذلك؟ بدون واجهة ، هذه الفئة قريبة من عديمة الفائدة. المكان الوحيد الذي يمكنني استخدامه فيه هو التطبيقات الخاصة. عند هذه النقطة يمكنني فقط إنشاء (أو إعادة استخدام) قائمة انتظار الأولوية الخاصة بي عند الحاجة. لا يمكنني الكشف عن واجهة برمجة تطبيقات عامة في الكود الخاص بي بهذا النوع في التوقيع لأنه سيتعطل بمجرد أن أحتاج إلى تبديله بتطبيق آخر.

تضمين التغريدة
حسنًا ، لدى Microsoft الكلمة الأخيرة هنا ، ليس هناك عدد قليل من الأشخاص الذين يعلقون على الموضوع. ونعلم أن:

أنا متأكد إلى حد ما من أن المراجعين / المهندسين المعماريين الآخرين لواجهة برمجة التطبيقات سيشاركونها كما راجعت من أجل الرأي مع عدد قليل: يجب أن يكون الاسم PriorityQueue ، ويجب ألا نقدم واجهة IHeap. يجب أن يكون هناك تنفيذ واحد بالضبط (على الأرجح عبر بعض الكومة).

تتم مشاركة هذا بواسطة karelz ، مدير مهندس البرامج ، و terrajobst ، مدير البرنامج ومالك هذا المستودع + بعض مراجعي واجهة برمجة التطبيقات.

على الرغم من أنني من الواضح أنني أحب نهج الواجهات ، كما هو مذكور بوضوح في المنشورات السابقة ، يمكنني أن أرى أنه صعب جدًا نظرًا لعدم امتلاكنا الكثير من القوة في هذه المناقشة. لقد أوضحنا نقاطنا ، لكننا مجرد بعض المعلقين. الرمز لا يخصنا على أي حال. ماذا يمكن ان نفعل ايضا؟

لماذا لا تغلق القضية بعد ذلك؟ بدون واجهة ، هذه الفئة قريبة من عديمة الفائدة.

لقد فعلت ما يكفي - بدلاً من كره عملي ، افعل شيئًا. قم بالعمل الفعلي. لماذا تحاول لومني على أي شيء؟ لوم نفسك على عدم نجاحك في إقناع الآخرين بوجهة نظرك.

ورجاء ، وفروا لي مثل هذا الكلام. إنه حقًا طفولي - افعل ما هو أفضل.

راجع للشغل ، يركز الاقتراح على الأشياء الشائعة بغض النظر عما إذا قدمنا ​​واجهة أم لا ، أو ما إذا كان الفصل يحمل اسم PriorityQueue أو Heap . لذا ركز على ما يهم حقًا هنا وأظهر لنا بعض التحيز للعمل إذا كنت تريد شيئًا ما.

pgolebiowski بالطبع هذا قرار مايكروسوفت. لكن من الأفضل تقديم واجهة برمجة التطبيقات التي تريد استخدامها والتي تلبي احتياجاتك. إذا تم رفضه فليكن. أنا فقط لا أرى ضرورة للتنازل عن الاقتراح.

أعتذر إذا فسرت تعليقاتي على أنها تلومك. لم يكن هذا بالتأكيد نيتي.

pgolebiowski لماذا لا تستخدم KeyValuePair<TKey,TValue> للمقبض؟

تضمين التغريدة

لماذا لا تستخدم KeyValuePair<TKey,TValue> للمقبض؟

  • حسنًا ، PriorityQueueHandle هو في الواقع _ عقدة كومة إقران _. يعرض خاصيتين - TKey Key و TValue Value . ومع ذلك ، فإنه يحتوي على منطق داخلي أكثر بكثير ، وهو داخلي فقط. يرجى الرجوع إلى تنفيذي لهذا . يحتوي على سبيل المثال أيضًا على مؤشرات إلى عقد أخرى في الشجرة (كل شيء سيكون داخليًا في CoreFX).
  • KeyValuePair عبارة عن هيكل ، لذلك يتم نسخه في كل مرة + لا يمكن توريثه.
  • لكن الشيء الرئيسي هو أن PriorityQueueHandle هي فئة معقدة نوعًا ما تصادف أنها تعرض واجهة برمجة التطبيقات العامة نفسها مثل KeyValuePair .

تضمين التغريدة

لكن من الأفضل تقديم واجهة برمجة التطبيقات التي تريد استخدامها والتي تلبي احتياجاتك. إذا تم رفضه فليكن. أنا فقط لا أرى ضرورة للتنازل عن الاقتراح.

  • هذا صحيح ، سأضع ذلك في الاعتبار وأرى ما سيحدث. karelz ، جنبًا إلى جنب مع الاقتراح ، هل يمكنك أيضًا تمرير بعض
  • ومع ذلك ، بغض النظر عن الحل الذي نختاره ، ستكون الوظيفة متشابهة جدًا وسيكون من المفيد مراجعتها. لأنه إذا تم رفض فكرة المقابض ولا يمكننا تحديث / إزالة العناصر بشكل صحيح (أو لا يمكن أن يكون لدينا TKey و TValue ) ، فإن هذه الفئة قريبة حقًا من أن تصبح عديمة الفائدة إذن - - كما هو الحال الآن في Java.
  • على وجه الخصوص ، إذا لم يكن لدينا واجهة ، فلن تتمكن مكتبة AlgoKit الخاصة بي من الحصول على نواة مشتركة لأكوام مع CoreFX ، الأمر الذي سيكون محزنًا حقًا بالنسبة لي.
  • ونعم ، إنه لأمر مدهش حقًا بالنسبة لي أن إضافة واجهة ينظر إليها على أنها عيب من قبل Microsoft.

لم يكن هذا بالتأكيد نيتي.

آسف ، خطأي بعد ذلك.

أسئلتي حول التصميم (إخلاء المسؤولية: هذه الأسئلة والتوضيحات ، لا توجد عمليات دفع قوية (حتى الآن)):

  1. هل نحتاج حقًا إلى PriorityQueueHandle ؟ ماذا لو توقعنا قيمًا فريدة في قائمة الانتظار؟

    • الدافع: يبدو أنه مفهوم متضمن إلى حد ما. إذا كان لدينا ، أود أن أفهم لماذا نحتاجها؟ كيف يساعد؟ أم أنها مجرد تفاصيل تنفيذ محددة تتسرب إلى سطح واجهة برمجة التطبيقات؟ هل ستشتري لنا هذا القدر من الأداء لدفع ثمن التعقيد في واجهة برمجة التطبيقات؟

  2. هل نحتاج Merge ؟ هل تمتلك المجموعات الأخرى ذلك؟ يجب ألا نضيف واجهات برمجة التطبيقات ، لمجرد أنها سهلة التنفيذ ، يجب أن يكون هناك بعض حالات الاستخدام الشائعة لواجهات برمجة التطبيقات.

    • ربما يكفي مجرد إضافة بعض التهيئة من IEnumerable<KeyValuePair<TKey, TValue>> ؟ + الاعتماد على Linq

  3. هل نحتاج comparer فائض؟ هل يمكننا دائما العودة إلى التخلف عن السداد؟ (إخلاء المسؤولية: أفتقد المعرفة / الخبرة في هذه الحالة ، لذا أسأل فقط)
  4. هل نحتاج إلى حمولات زائدة keySelector ؟ أعتقد أننا يجب أن نقرر ما إذا كنا نريد أن تكون لنا الأولوية كجزء من القيمة ، أم كشيء منفصل. تبدو الأولوية المنفصلة طبيعية أكثر قليلاً بالنسبة لي ، لكن ليس لدي رأي قوي. هل نعرف إيجابيات مقابل سلبيات؟

نقاط قرار منفصلة / موازية:

  1. اسم الفئة PriorityQueue مقابل Heap
  2. تقديم IHeap و التحميل الزائد للمنشئ؟

تقديم IHeap والحمل الزائد للمنشئ؟

يبدو أن الأمور قد استقرت بما يكفي لأرمي بسنتين .... أنا أحب الواجهة شخصيًا. إنه يلخص تفاصيل التنفيذ بعيدًا عن واجهة برمجة التطبيقات (والوظيفة الأساسية الموصوفة بواسطة واجهة برمجة التطبيقات) بطريقة تبسط ، في رأيي ، الهيكل وتتيح أكبر قدر من قابلية الاستخدام.

ما ليس لدي رأي قوي بشأنه هو ما إذا كنا نقوم بالواجهة في نفس الوقت مثل PQueue / Heap / ILikeThisItemMoreThanThisItemList أو إذا أضفناها لاحقًا. إن الحجة القائلة بأن واجهة برمجة التطبيقات قد تكون "في حالة تغير مستمر" وبالتالي يجب أن نطلقها كصف دراسي أولاً حتى نحصل على تعليقات هي بالتأكيد حجة صالحة لا أختلف معها. يصبح السؤال بعد ذلك عندما يعتبر "مستقرًا" بدرجة كافية لإضافة واجهة. تم ذكر IList و IDictionary على أنهما متأخران عن واجهات برمجة التطبيقات الخاصة بالتطبيقات المتعارف عليها والتي أضفناها منذ وقت طويل ، فما هي الفترة الزمنية التي تعتبر فترة راحة مقبولة؟

إذا تمكنا من تحديد تلك الفترة بقدر معقول من اليقين والتأكد من عدم حظرها بشكل غير مقبول ، فأنا لا أرى مشكلة في شحن هذا الشيء المكدس بهيكل البيانات بدون واجهة. ثم بعد هذه الفترة الزمنية ، يمكننا فحص الاستخدام والنظر في إضافة واجهة.

ونعم ، إنه لأمر مدهش حقًا بالنسبة لي أن إضافة واجهة ينظر إليها على أنها عيب من قبل Microsoft.

هذا لأنه من نواح كثيرة يعتبر غير موات. إذا قمنا بشحن واجهة ، فسيتم ذلك إلى حد كبير. ليس هناك الكثير من التذبذب من أجل التكرار على واجهة برمجة التطبيقات تلك ، لذلك من الأفضل أن نتأكد من أنها صحيحة في المرة الأولى وستظل مناسبة لسنوات عديدة قادمة. يفتقد بعض لطيفة إلى أن يكون وظيفة هو مكان وطريقة أفضل ليكون في من أن تكون عالقة مع واجهة يحتمل أن تكون غير كافية حيث سيكون يا ما بين الجميل أن يكون هذا مجرد تغيير واحد صغير من شأنها أن تجعل من كل شيء أفضل.

شكرًا لك على المدخلات ، karelz وianhays!

السماح بالتكرارات

هل نحتاج حقًا إلى PriorityQueueHandle ؟ ماذا لو توقعنا قيمًا فريدة في قائمة الانتظار؟

الدافع: يبدو أنه مفهوم متضمن إلى حد ما. إذا كان لدينا ، أود أن أفهم لماذا نحتاجها؟ كيف يساعد؟ أم أنها مجرد تفاصيل تنفيذ محددة تتسرب إلى سطح واجهة برمجة التطبيقات؟ هل ستشتري لنا هذا القدر من الأداء لدفع ثمن التعقيد في واجهة برمجة التطبيقات؟

لا ، لسنا بحاجة لذلك. واجهة برمجة تطبيقات قائمة الانتظار ذات الأولوية المقترحة أعلاه قوية جدًا ومرنة جدًا. وهو يقوم على افتراض أن العناصر والأولويات يمكن أن تتكرر . بسبب هذا الافتراض ، من الضروري وجود مقبض لتتمكن من إزالة العقدة الصحيحة أو تحديثها. ومع ذلك ، إذا وضعنا قيودًا على أن العناصر يجب أن تكون فريدة ، فيمكننا تحقيق نفس النتيجة كما هو مذكور أعلاه باستخدام واجهة برمجة تطبيقات أبسط وبدون الكشف عن فئة داخلية ( PriorityQueueHandle ) ، وهي بالفعل ليست مثالية.

لنفترض أننا نسمح فقط بالعناصر الفريدة. لا يزال بإمكاننا دعم جميع السيناريوهات السابقة والحفاظ على الأداء الأمثل. أبسط API:

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

قريبًا ، سيكون هناك Dictionary<TElement, InternalNode> أساسي. يمكن إجراء اختبار ما إذا كانت قائمة انتظار الأولوية تحتوي على عنصر بشكل أسرع من النهج السابق. يعد تحديث العناصر وإزالتها أمرًا مبسطًا بشكل كبير ، حيث يمكننا دائمًا الإشارة إلى عنصر مباشر في قائمة الانتظار.

ربما لا يستحق السماح بالنسخ المكررة المتاعب وما ورد أعلاه كافٍ. أعتقد أنني أحب ذلك. ماذا تعتقد؟

دمج

هل نحتاج Merge ؟ هل تمتلك المجموعات الأخرى ذلك؟ يجب ألا نضيف واجهات برمجة التطبيقات ، لمجرد أنها سهلة التنفيذ ، يجب أن يكون هناك بعض حالات الاستخدام الشائعة لواجهات برمجة التطبيقات.

يوافق على. نحن لا نحتاجها. يمكننا دائمًا إضافة API ولكن لا يمكننا إزالتها. أنا موافق على إزالة هذه الطريقة و (من المحتمل) إضافتها لاحقًا (إذا كانت هناك حاجة).

المقارن

هل نحن بحاجة مقارنة الزائد؟ هل يمكننا دائما العودة إلى التخلف عن السداد؟ (إخلاء المسؤولية: أفتقد المعرفة / الخبرة في هذه الحالة ، لذا أسأل فقط)

أعتقد أن هذا مهم جدًا لسببين:

  • يرغب المستخدم في ترتيب عناصره بترتيب تصاعدي أو تنازلي. لا تخبرنا الأولوية القصوى عن كيفية إجراء الطلب.
  • إذا تخطينا المقارنة ، فإننا نفرض على المستخدم أن يقدم لنا دائمًا فئة تنفذ IComparable .

بالإضافة إلى أنه يتوافق مع واجهة برمجة التطبيقات الحالية. إلقاء نظرة على SortedDictionary .

محدد

هل نحتاج keySelector الزائدة؟ أعتقد أننا يجب أن نقرر ما إذا كنا نريد أن تكون لنا الأولوية كجزء من القيمة ، أم كشيء منفصل. تبدو الأولوية المنفصلة طبيعية أكثر قليلاً بالنسبة لي ، لكن ليس لدي رأي قوي. هل نعرف إيجابيات مقابل سلبيات؟

أنا أيضا أحب أولوية منفصلة. بصرف النظر عن ذلك ، من الأسهل تنفيذ ذلك ، وتحسين الأداء واستخدام الذاكرة ، وواجهة برمجة تطبيقات أكثر سهولة ، وعمل أقل للمستخدم (لا حاجة إلى تنفيذ IComparable ).

بخصوص المحدد الآن ...

الايجابيات

هذا ما يجعل قائمة انتظار الأولوية هذه مرنة. يسمح للمستخدمين بالحصول على:

  • العناصر وأولوياتها كعناصر منفصلة
  • العناصر (الفئات المعقدة) التي لها أولويات في مكان ما بداخلها
  • منطق خارجي يسترجع الأولوية لعنصر معين
  • العناصر التي تنفذ IComparable

إنه يسمح إلى حد كبير بكل تكوين يمكنني التفكير فيه. أجده مفيدًا ، حيث يمكن للمستخدمين "التوصيل والتشغيل". إنها أيضًا بديهية إلى حد ما.

سلبيات

  • هناك المزيد لنتعلمه.
  • المزيد من API. مُنشئان إضافيان ، طريقة إضافية Enqueue وطريقة Update .
  • إذا قررنا فصل العنصر والأولوية أو ضمهما ، فإننا نفرض على بعض المستخدمين (الذين لديهم بياناتهم بتنسيق مختلف) تكييف التعليمات البرمجية الخاصة بهم لاستخدام بنية البيانات هذه.

نقاط قرار منفصلة / موازية

اسم الفئة PriorityQueue مقابل Heap

قدم IHeap وحمل المُنشئ الزائد.

  • يبدو أن PriorityQueue يجب أن يكون جزءًا من CoreFX ، بدلاً من Heap .
  • فيما يتعلق بواجهة IHeap - نظرًا لأنه يمكن تنفيذ قائمة انتظار ذات أولوية بشيء مختلف عن الكومة ، فربما نرغب في عدم كشفها بهذه الطريقة. ومع ذلك ، قد نحتاج إلى IPriorityQueue .

إذا قمنا بشحن واجهة ، فسيتم ذلك إلى حد كبير. ليس هناك الكثير من التذبذب من أجل التكرار على واجهة برمجة التطبيقات تلك ، لذلك من الأفضل أن نتأكد من أنها صحيحة في المرة الأولى وستظل مناسبة لسنوات عديدة قادمة. يعد فقدان بعض الوظائف اللطيفة مكانًا أفضل للتواجد فيه بدلاً من أن تكون عالقًا بواجهة يحتمل أن تكون غير كافية حيث سيكون من الجيد أن يكون لديك هذا التغيير الصغير الذي من شأنه أن يجعل كل شيء أفضل.

موافق تماما!

عناصر قابلة للمقارنة

إذا أضفنا افتراضًا آخر: يجب أن تكون العناصر قابلة للمقارنة ، فإن واجهة برمجة التطبيقات تكون أبسط. لكن مرة أخرى ، إنها أقل مرونة.

الايجابيات

  • لا حاجة ل IComparer .
  • لا حاجة للمحدد.
  • طريقة Enqueue و Update .
  • نوع عام واحد بدلاً من اثنين.

سلبيات

  • لا يمكن أن يكون لدينا عنصر وأولوية ككائنات منفصلة. يحتاج المستخدمون إلى توفير فئة غلاف جديدة إذا كانت متوفرة بهذا التنسيق.
  • يحتاج المستخدمون دائمًا إلى تنفيذ IComparable قبل أن يتمكنوا من استخدام قائمة انتظار الأولوية هذه.
  • إذا كانت لدينا عناصر وأولويات منفصلة ، فيمكننا تنفيذها بشكل أسهل ، مع أداء أفضل واستخدام الذاكرة - القاموس الداخلي <TElement, InternalNode> ولديك InternalNode يحتوي على 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();
}

كل شيء هو مقايضة. نزيل بعض الميزات ، ونفقد بعض المرونة ، لكن ربما لا نحتاج إلى ذلك لإرضاء 95٪ من مستخدمينا.

يعجبني أيضًا نهج الواجهة لأنه أكثر مرونة ويوفر المزيد من الاستخدامات ، لكنني أتفق أيضًا مع karelz و ianhays على أننا يجب أن ننتظر حتى يتم استخدام واجهة برمجة التطبيقات للفئة الجديدة ونحصل على تعليقات حتى نقوم بالفعل بشحن الواجهة ثم نقوم يتم تخزين هذا الإصدار ولا يمكن تغييره دون كسر العملاء الذين استخدموه بالفعل.

أيضًا حول المقارنات ، أعتقد أنها تتبع واجهات BCL الأخرى ولا أحب حقيقة أن المستخدمين بحاجة إلى توفير فئة غلاف جديدة. يعجبني حقًا التحميل الزائد للمنشئ الذي يتلقى نهجًا للمقارنة واستخدام هذا المقارنة داخل جميع المقارنات التي يجب إجراؤها داخليًا ، إذا لم يكن المقارنة المقدمة أو المقارنة فارغة ، فاستخدم المقارنة الافتراضية.

pgolebiowski شكرًا لمقترح API المفصل

حسنًا ، أنا مقتنع بالمقارنة ، إنها منطقية ومتسقة.
ما زلت ممزقًا عن المحدد - IMO يجب أن نحاول الاستغناء عنه - دعنا نقسمه إلى متغيرين.

ج #
فئة عامة أولوية
: I عدد لا يحصى ،
I عدد لا يحصى <(عنصر TElement ، أولوية الأولوية)> ،
مجموعة IReadOnlyCollection <(عنصر TElement ، أولوية الأولوية)>
// لم يتم تضمين المجموعة عمدًا
{
public PriorityQueue ()؛
public PriorityQueue (IComparerالمقارنة) ؛

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

//
// جزء المحدد
//
public PriorityQueue (Funcالأفضلية) ؛
public PriorityQueue (Funcمحدد الأولوية ، IComparerالمقارنة) ؛

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

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

}
""

أسئلة مفتوحة:

  1. اسم الفئة PriorityQueue مقابل Heap
  2. تقديم IHeap وتحميل المُنشئ الزائد؟ (هل ننتظر لاحقًا؟)
  3. تقديم IPriorityQueue ؟ (هل يجب أن ننتظر لاحقًا - مثال IDictionary )
  4. استخدم المحدد (للأولوية المخزنة داخل القيمة) أم لا (اختلاف 5 واجهات برمجة التطبيقات)
  5. استخدم tuples (TElement element, TPriority priority) مقابل KeyValuePair<TPriority, TElement>

    • هل يجب أن يكون لدى Peek و Dequeue بدلاً من out وسيطة بدلاً من tuple؟

سأحاول تشغيله بواسطة مجموعة مراجعة API غدًا للحصول على تعليقات مبكرة.

تم إصلاح اسم حقل tuple Peek و Dequeue إلى priority (شكرًا pgolebiowski على الإشارة إليه).
أعلى مشاركة محدثة مع أحدث اقتراح أعلاه.

أنا أؤيد تعليق pgolebiowski لقيادتها إلى الأمام!

أسئلة مفتوحة:

أعتقد أنه يمكننا الحصول على واحدة أخرى هنا:

  1. نقتصر على العناصر الفريدة فقط (لا تسمح بالتكرارات).

نقطة جيدة ، تم التقاطها على أنها "افتراضات" في أعلى مشاركة ، بدلاً من كونها سؤال مفتوح. العكس هو القبيح جدا لواجهة برمجة التطبيقات.

هل يجب أن نعيد bool من Remove ؟ وأيضًا الأولوية كـ out priority arg؟ (أعتقد أننا أضفنا أحمالًا زائدة مماثلة مؤخرًا إلى هياكل البيانات الأخرى)

على غرار Contains - أراهن أن شخصًا ما سيرغب في إخراج الأولوية منه. قد نرغب في إضافة حمل زائد بـ out priority .

ما زلت على رأيي (على الرغم من أنني لم أصرح بذلك بعد) بأن Heap قد يعني تطبيقًا ، وسأدعم على النحو الواجب استدعاء هذا PriorityQueue . (حتى أنني أؤيد اقتراحًا لفئة أقل حجمًا من Heap والتي لا تسمح بالعناصر المكررة ، ولا تسمح بالتحديث ، وما إلى ذلك (يتماشى أكثر مع الاقتراح الأصلي) لكنني لا أتوقع ذلك يحدث).

بالتأكيد لا ينبغي استخدام KeyValuePair<TPriority, TElement> ، حيث من المتوقع أن تكون الأولويات المكررة ، وأعتقد أن KeyValuePair<TElement, TPriority> أمر محير أيضًا ، لذلك سأدعم عدم استخدام KeyValuePair على الإطلاق ، وأيًا منهما باستخدام مجموعات عادية ، أو خارج المعلمات للأولويات (أنا شخصياً أحب استخدام المعلمات ، لكنني لست مضطربًا).

إذا رفضنا التكرارات ، فنحن بحاجة إلى تحديد سلوك محاولة إعادة إضافتها بأولويات مختلفة / متغيرة.

لا أؤيد اقتراح المُحدِّد ، لسبب بسيط هو أنه يشير إلى التكرار ، وسيعيد إنشاء الالتباس على النحو الواجب. إذا كانت أولوية العنصر مخزنة معه ، فسيتم تخزينها في مكانين ، وإذا أصبحت غير متزامنة (أي أن شخصًا ما نسي استدعاء طريقة Update(TElement) عديمة الجدوى) فحينئذٍ معاناة كبيرة سيضمن. إذا كان المحدِّد جهاز كمبيوتر ، فنحن منفتحون على الأشخاص الذين يضيفون عنصرًا عن قصد ، ثم يغيرون القيم التي يتم حسابهم منها ، والآن إذا حاولوا إعادة إضافته ، فهناك ثروة من الأشياء التي يمكن أن تسوء حسب بشأن قرار ما يحدث عند حدوث ذلك. عند مستوى أعلى قليلاً ، قد تؤدي المحاولة بالرغم من ذلك إلى إضافة نسخة متغيرة من العنصر ، حيث لم تعد مساوية لما كانت عليه من قبل (هذه مشكلة عامة تتعلق بالمفاتيح القابلة للتغيير ، لكنني أعتقد أن الفصل بين الأولوية والعنصر تساعد على تجنب المشكلة المحتملة).

المحدد نفسه عرضة لتغيير السلوك ، وهي طريقة أخرى يمكن للمستخدمين من خلالها كسر كل شيء دون تفكير. أعتقد أنه من الأفضل بكثير أن يقدم المستخدمون الأولويات بشكل صريح. المشكلة الكبيرة التي أراها مع هذا ، هي أنها تنقل أن الإدخالات المكررة مسموح بها ، لأننا نعلن عن زوج ، وليس عنصرًا. ومع ذلك ، فإن الوثائق المضمنة المعقولة وقيمة الإرجاع bool على Enqueue يجب أن تعالج هذا الأمر بشكل تافه. قد يكون أفضل من قيمة منطقية هو إرجاع الأولوية القديمة / الجديدة للعنصر (على سبيل المثال ، إذا كان Enqueue يستخدم الأولوية المقدمة حديثًا ، أو الحد الأدنى من الاثنين ، أو الأولوية القديمة) ، لكنني أعتقد أن Enqueue يجب أن تفشل فقط إذا حاولت إعادة إضافة شيء ما ، وبالتالي يجب فقط إرجاع bool يشير إلى النجاح. هذا يبقي Enqueue و Update منفصلين تمامًا ومعرّفين جيدًا.

سأدعم عدم استخدام KeyValuePair على الإطلاق ، وإما استخدام المجموعات العادية

أنا معك في المجموعات.

إذا رفضنا التكرارات ، فنحن بحاجة إلى تحديد سلوك محاولة إعادة إضافتها بأولويات مختلفة / متغيرة.

  • أرى تشابهًا مع المفهرس داخل Dictionary . هناك ، عندما تفعل dictionary["something"] = 5 ، يتم تحديثه إذا كان "something" مفتاحًا هناك سابقًا. إذا لم يكن موجودًا ، فسيتم إضافته فقط.
  • ومع ذلك ، فإن طريقة Enqueue مماثلة بالنسبة لي لطريقة Add في القاموس. مما يعني أنه يجب أن يطرح استثناء.
  • مع الأخذ في الاعتبار النقاط المذكورة أعلاه ، يمكننا التفكير في إضافة مفهرس إلى قائمة انتظار الأولوية لدعم السلوك الذي تفكر فيه.
  • ولكن في المقابل ، قد لا يكون المفهرس شيئًا يعمل مع مفهوم قوائم الانتظار.
  • وهو ما يقودنا إلى استنتاج مفاده أن طريقة Enqueue يجب أن تطرح استثناءً فقط إذا أراد شخص ما إضافة عنصر مكرر. وبالمثل ، يجب أن تطرح طريقة Update استثناءً إذا أراد شخص ما تحديث أولوية عنصر غير موجود.
  • وهو ما يقودنا إلى حل جديد - أضف طريقة TryUpdate التي تُرجع بالفعل bool .

أنا لا أؤيد اقتراح المحدد ، لسبب بسيط هو أنه يتضمن التكرار

أليس هذا هو أن المفتاح لم يتم نسخه فعليًا (إذا كان موجودًا) ، ولكن المحدد يظل مجرد وظيفة يتم استدعاؤها عند الحاجة إلى تقييم الأولويات؟ أين الفائض؟

أعتقد أن فصل الأولوية عن العنصر سيساعد في تجنب المشكلة المحتملة

المشكلة الوحيدة هي الحالة عندما لا يكون لدى العميل الأولوية المادية. لا يمكنه فعل الكثير بعد ذلك.

أعتقد أنه من الأفضل بكثير أن يقدم المستخدمون الأولويات بشكل صريح. المشكلة الكبيرة التي أراها مع هذا ، هي أنها تنقل أن الإدخالات المكررة مسموح بها ، لأننا نعلن عن زوج ، وليس عنصرًا.

أرى بعض المشكلات في هذا الحل ، ولكن ليس بالضرورة سبب نقله للسماح بإدخالات مكررة. أعتقد أنه يجب تطبيق منطق "عدم التكرار" على TElement فقط - الأولوية هي مجرد قيمة هنا.

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

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

VisualMelon هل هذا منطقي؟

تضمين التغريدة

أوافق تمامًا على إضافة TryUpdate ، و Update رمي أمر منطقي بالنسبة لي. كنت أفكر أكثر على غرار ISet فيما يتعلق بـ Enqueue (بدلاً من IDictionary ). سيكون الرمي منطقيًا أيضًا ، لا بد أنني فاتني هذه النقطة ، لكنني أعتقد أن العائد bool ينقل "ضبط" النوع. ربما يكون TryEnqueue مرتبًا أيضًا (مع رمي Add )؟ سيتم تقدير A TryRemove أيضًا (يفشل إذا كان فارغًا). فيما يتعلق بالنقطة الأخيرة ، نعم ، هذا هو السلوك الذي كنت أفكر فيه أيضًا. أفترض أن القياس مع IDictionary أفضل من ISet عند الانعكاس ، وهذا يجب أن يكون واضحًا بدرجة كافية. (للتلخيص: أنا أؤيد كل شيء يتم طرحه وفقًا لاقتراحك ، ولكن وجود Try* أمر لا بد منه إذا كان هذا هو الحال ؛ أنا أتفق أيضًا مع تصريحاتك فيما يتعلق بشروط الفشل).

فيما يتعلق بالمفهرس ، أعتقد أنك محق في أنه لا يتناسب حقًا مع مفهوم قائمة الانتظار ، فلن أؤيد ذلك. إذا كان هناك أي شيء ، فستكون طريقة جيدة لقائمة الانتظار أو التحديث بالترتيب.

أليس هذا هو أن المفتاح لم يتم نسخه فعليًا (إذا كان موجودًا) ، ولكن المحدد يظل مجرد وظيفة يتم استدعاؤها عند الحاجة إلى تقييم الأولويات؟ أين الفائض؟

أنت محق ، لقد أخطأت في قراءة تحديث الاقتراح (الجزء المتعلق بتخزينه في العنصر). مع الأخذ في الاعتبار أنه يتم استدعاء المحدد عند الطلب (والذي يجب توضيحه في أي وثائق ، حيث يمكن أن يكون له آثار على الأداء) ، فإن النقاط لا تزال قائمة أن نتيجة الوظيفة يمكن أن تتغير دون استجابة بنية البيانات ، مما يترك اثنان غير متزامنتين ما لم يتم استدعاء Update . والأسوأ من ذلك ، إذا غيّر المستخدم الأولوية الفعالة لعناصر متعددة ، وقام بتحديث واحد منهم فقط ، فسينتهي الأمر بهيكل البيانات اعتمادًا على التغييرات "غير الملتزم بها" عند تحديدها من عناصر "غير محدثة" (لم أنظر في ذلك) تنفيذ DataStructure المقترح بالتفصيل ، لكنني أعتقد أن هذه مشكلة بالضرورة لأي تحديث O(<n) ). يؤدي إجبار المستخدم على تحديث أي أولويات بشكل صريح إلى معالجة ذلك ، مما يؤدي بالضرورة إلى نقل بنية البيانات من حالة متسقة إلى أخرى.

لاحظ أن جميع نقاط قبضتي المعلنة حتى الآن مع اقتراح Selector تتعلق بقوة واجهة برمجة التطبيقات: أعتقد أن المحدد يجعل من السهل استخدامه بشكل غير صحيح. ومع ذلك ، بغض النظر عن قابلية الاستخدام ، من الناحية المفاهيمية ، لا ينبغي أن تعرف العناصر الموجودة في قائمة الانتظار أولويتها. من المحتمل أن يكون غير ذي صلة بهم ، وإذا انتهى الأمر بالمستخدم إلى تغليف عناصره struct Queable<T>{} أو شيء ما ، فهذا يبدو وكأنه فشل في توفير واجهة برمجة تطبيقات خالية من الاحتكاك (بقدر ما يؤلمني لاستخدام المصطلح).

يمكنك بالطبع أن تجادل بأنه بدون المحدد ، يقع العبء على المستخدم للاتصال بالمحدد ، لكنني أعتقد أنه إذا عرفت العناصر أولويتها ، فسيتم كشفها (على أمل) بشكل مرتب (أي خاصية) ، وإذا لم يفعلوا ذلك t ، فلا داعي للقلق بشأنه (إنشاء الأولويات أثناء التنقل ، وما إلى ذلك). هناك الكثير من السباكة التي لا معنى لها المطلوبة لدعم المحدد إذا كنت لا تريد ذلك ، ثم لتمرير الأولويات إذا كان لديك بالفعل "محدد" محدد جيدًا. يغري المحدد المستخدم لكشف هذه المعلومات (ربما بشكل غير مفيد) ، بينما توفر الأولويات المنفصلة واجهة شفافة للغاية لا أتصور أنها ستؤثر على قرارات التصميم.

الحجة الوحيدة التي يمكنني التفكير فيها حقًا لصالح المحدد هي أنه يعني أنه يمكنك استخدام تمرير PriorityQueue حوله ، ويمكن لأجزاء أخرى من البرنامج استخدامه دون معرفة كيفية حساب الأولويات. سأفكر في هذا لفترة أطول قليلاً ، ولكن بالنظر إلى الملاءمة "المتخصصة" ، فإن هذا يبدو لي مكافأة قليلة مقابل النفقات العامة الثقيلة بشكل معقول في الحالات الأكثر عمومية.

_Edit: مع الأخذ في الاعتبار المزيد حول هذا الموضوع ، سيكون من الجيد جدًا أن يكون لديك PriorityQueue s مستقل بذاته يمكنك فقط طرح Elements فيه ، لكنني أؤكد أن تكلفة العمل على حل هذا الأمر ستكون رائعة ، بينما تكلفة التقديم ستكون أقل بكثير.

لقد كنت أقوم ببعض العمل في البحث من خلال مصادر أكواد .NET مفتوحة المصدر لرؤية أمثلة من العالم الحقيقي لكيفية استخدام قوائم الانتظار ذات الأولوية. المشكلة الصعبة هي تصفية أي مشاريع طلابية وتدريب الكود المصدري.

الاستخدام 1: Roslyn Notification Service
https://github.com/dotnet/roslyn/
يشتمل برنامج التحويل البرمجي Roslyn على تنفيذ قائمة انتظار ذات أولوية خاصة يسمى "PriorityQueue" والذي يبدو أنه يحتوي على تحسين محدد للغاية - قائمة انتظار كائن لإعادة استخدام الكائنات في قائمة الانتظار لتجنب تجميعها للقمامة. تقوم طريقة Enqueue_NoLock بإجراء تقييم على current.Value.MinimumRunPointInMS <entry.Value.MinimumRunPointInMS لتحديد مكان وضع العقدة الجديدة في قائمة الانتظار. يناسب أي من التصميمين الرئيسيين لقائمة الانتظار ذات الأولوية المقترحين هنا (وظيفة المقارنة / المفوض مقابل الأولوية الصريحة) سيناريو الاستخدام هذا.

الاستخدام 2: Lucene.net
https://github.com/apache/lucenenet
يمكن القول إن هذا هو أكبر مثال على استخدام PriorityQueue يمكن أن أجده في .NET. يعد Apache Lucene.net منفذًا كاملًا للشبكة لمكتبة محرك بحث Lucene الشهيرة. نستخدم إصدار Java في شركتي ، ووفقًا لموقع Apache على الويب ، تستخدم بعض الأسماء الكبيرة إصدار .NET. يوجد عدد كبير من مفترقات مشروع .NET في Github.

يتضمن Lucene تطبيق PriorityQueue الخاص به والذي تم تصنيفه فرعيًا بواسطة عدد من قوائم انتظار الأولوية "المتخصصة": HitQueue و TopOrdAndFloatQueue و PhraseQueue و SuggestWordQueue. بالإضافة إلى ذلك ، يُنشئ المشروع "PriorityQueue" مباشرةً في عدد من الأماكن.

تطبيق PriorityQueue من Lucene ، المرتبط أعلاه ، مشابه جدًا لواجهة برمجة تطبيقات قائمة انتظار الأولوية الأصلية المنشورة في هذه المشكلة. يتم تعريفه على أنه "PriorityQueue""ويقبل IComparerالمعلمة في منشئها. تتضمن الأساليب والخصائص العد ، المسح ، العرض (قائمة الانتظار / الدفع) ، الاستطلاع (dequeue / pop) ، نظرة خاطفة ، إزالة (يزيل العنصر المطابق الأول الموجود في قائمة الانتظار) ، إضافة (مرادف للعرض). ومن المثير للاهتمام أنها توفر أيضًا دعم التعداد لقائمة الانتظار.

يتم تحديد الأولوية في PriorityQueue من Lucene بواسطة المقارنة التي تم تمريرها إلى المنشئ ، وإذا لم يتم تمرير أي منها ، فإنها تفترض أن الكائنات التي تمت مقارنتها تنفذ ICويستخدم تلك الواجهة لإجراء المقارنة. تصميم API الأصلي المنشور هنا مشابه ، باستثناء أنه يعمل مع أنواع القيم أيضًا.

هناك عدد كبير من أمثلة الاستخدام من خلال قاعدة الرموز الخاصة بهم ، SloppyPhraseScorer هي واحدة منهم.

ينشئ مُنشئ SloppyPhraseScorer PhraseQueue (pq) جديدًا ، وهو أحد الفئات الفرعية المخصصة الخاصة به من PriorityQueue. يتم إنشاء مجموعة من PhrasePositions ، والتي تبدو وكأنها غلاف لمجموعة من التعيينات ، وموضع ، ومجموعة من المصطلحات. تعدد طريقة FillQueue مواضع العبارة وتدرجها في قائمة. يستدعي PharseFreq () وظيفة AdvancePP ، وعلى مستوى عالٍ ، يبدو أنه يقوم بإزالة قائمة الصفوف وتحديث أولوية العنصر ، ثم الإدراج مرة أخرى في قائمة الانتظار. يتم تحديد الأولوية نسبيًا (باستخدام المقارنة) وليس بشكل صريح (لا يتم "تمرير الأولوية" كمعامل ثانٍ أثناء قائمة الانتظار).

يمكنك أن ترى أنه بناءً على تنفيذها لـ PhraseQueue ، فإن قيمة المقارنة التي تم تمريرها عبر المُنشئ (مثل عدد صحيح) قد لا تقطعها. تقوم وظيفة المقارنة الخاصة بهم ("LessThan") بتقييم ثلاثة حقول مختلفة: PhrasePositions.doc و PhrasePositions.position و PhrasePositions.offset.

الاستخدام 3: تطوير اللعبة
لم أنتهي من البحث في أمثلة الاستخدام في هذا الفضاء ، لكنني رأيت عددًا قليلاً من الأمثلة على استخدام .NET PriorityQueue المخصص في تطوير اللعبة. بشكل عام للغاية ، تميل هذه إلى التجمع حول تحديد المسار كحالة الاستخدام الرئيسية (Dijkstra's). يمكنك العثور على الكثير من الأشخاص يسألون عن كيفية تنفيذ خوارزميات اكتشاف المسار في .NET بسبب Unity 3D .

لا تزال بحاجة للحفر في هذه المنطقة ؛ رأيت بعض الأمثلة ذات الأولوية الصريحة التي يتم سردها وبعض الأمثلة باستخدام المقارنة / IComparable.

في ملاحظة منفصلة ، كان هناك بعض النقاش حول العناصر الفريدة ، وإزالة العناصر الصريحة ، وتحديد ما إذا كان هناك عنصر معين.

قوائم الانتظار ، كهيكل بيانات ، تدعم بشكل عام خاصية Enqueuing و Dequeuing. إذا توجهنا إلى مسار توفير عمليات أخرى شبيهة بالمجموعة / القائمة ، أتساءل عما إذا كنا نصمم بالفعل بنية بيانات مختلفة تمامًا - شيء يشبه قائمة مرتبة من المجموعات. إذا كان لدى المتصل حاجة بخلاف Enqueue و Dequeue و Peek ، فربما يحتاجون إلى شيء آخر غير قائمة انتظار الأولوية؟ قائمة الانتظار ، حسب التعريف ، تعني الإدراج في قائمة الانتظار والإزالة المطلوبة من قائمة الانتظار ؛ ليس أكثر من ذلك.

تضمين التغريدة

أنا أقدر جهودك في تصفح مستودعات أخرى والتحقق من كيفية تسليم وظيفة قائمة الانتظار ذات الأولوية هناك. ومع ذلك ، ستستفيد المنظمة البحرية الدولية في هذه المناقشة من تقديم مقترحات تصميم محددة . هذا الخيط صعب المتابعة بالفعل وسرد قصة طويلة دون أي استنتاجات تجعل الأمر أكثر صعوبة.

قوائم الانتظار [...] تدعم بشكل عام خاصية Enqueuing و Dequeuing. [...] إذا كان المتصل لديه حاجة بخلاف Enqueue و Dequeue و Peek ، فربما يحتاجون إلى شيء آخر غير قائمة انتظار الأولوية؟ قائمة الانتظار ، حسب التعريف ، تعني الإدراج في قائمة الانتظار والإزالة المطلوبة من قائمة الانتظار ؛ ليس أكثر من ذلك.

  • ما هو الاستنتاج؟ عرض؟
  • إنه بعيد جدا عن الحقيقة. حتى خوارزمية Dijkstra التي ذكرتها تستخدم تحديث أولويات العناصر. وما هو مطلوب لتحديث عناصر محددة مطلوب أيضًا لإزالة عناصر محددة.

مناقشة رائعة. بحثebickle مفيد للغاية IMO!
ebickle هل لديك استنتاج بشأن [2] lucene.net - هل يناسب اقتراحنا الأخير الاستخدامات أم لا؟ (أتمنى ألا يفوتني الوصف التفصيلي الخاص بك)

يبدو أننا نحتاج إلى متغيرات Try* من أعلى + IsEmpty + TryPeek / TryDequeue + EnqueueOrUpdate ؟ أفكار؟
ج #
منطقية عامة 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

""

تضمين التغريدة

يبدو أننا بحاجة إلى المتغيرات "جرب *" من أعلى

نعم بالضبط.

هل يجب إرجاع الحالة المنطقية للائحة في قائمة الانتظار مقابل التحديث؟

إذا أردنا إعادة الحالات في كل مكان ، ثم في كل مكان. بالنسبة لهذه الطريقة بالذات ، أعتقد أنه يجب أن يكون صحيحًا إذا نجحت أي من العمليتين ( Enqueue أو Update ).

بالنسبة للبقية - أوافق ببساطة: ابتسم:

سؤال واحد فقط - لماذا ref بدلاً من out ؟ لست متأكد:

  • لا نحتاج إلى تهيئته قبل الدخول إلى الوظيفة.
  • لا يتم استخدامه للطريقة (يخرج فقط).

إذا أردنا إعادة الحالات في كل مكان ، ثم في كل مكان. بالنسبة لهذه الطريقة بالذات ، أعتقد أنه يجب أن يكون صحيحًا إذا نجحت أي من العمليتين ( Enqueue أو Update ).

ستعود دائمًا إلى حقيقة ، هذه ليست فكرة جيدة. يجب أن نستخدم void في مثل هذه الحالة IMO. وإلا فسيتم الخلط بين الأشخاص وسيتحققون من قيمة الإرجاع وسيضيفون رمزًا عديم الفائدة لن يتم تنفيذه أبدًا. (إلا إذا فاتني شيء)

المرجع مقابل خارج المتفق عليه ، لقد ناقشت ذلك بنفسي. ليس لدي رأي قوي / خبرة كافية لاتخاذ القرار بنفسي. يمكننا أن نطلب من مراجعي API / ننتظر المزيد من التعليقات.

سيعود دائما صحيحا

أنت على حق يا سيئة. آسف.

ليس لدي رأي قوي / خبرة كافية لاتخاذ القرار بنفسي.

ربما أفتقد شيئًا ما ، لكني أجده بسيطًا جدًا. إذا استخدمنا ref ، فإننا نقول أساسًا أن Peek و Dequeue يريدان استخدام TElement و TPriority ما ( أعني قراءة تلك الحقول). هذا ليس هو الحال حقًا - من المفترض أن تقوم طرقنا بتعيين قيم لتلك المتغيرات فقط (وهي في الواقع مطلوبة للقيام بذلك من قبل المترجم).

تم تحديث المنشور الأعلى باستخدام واجهات برمجة تطبيقات Try* الخاصة بي
تمت إضافة سؤالين مفتوحين:

  • [6] هل رمي Peek و Dequeue مفيد على الإطلاق؟
  • [7] TryPeek و TryDequeue - يجب استخدام ref أو out args؟

المرجع مقابل الخروج - أنت على حق. كنت أقوم بالتحسين لتجنب التهيئة في حالة إرجاع القيمة false. كان ذلك غبيًا ومفاجئًا مني - تحسين سابق لأوانه. سوف أقوم بتغييره للخارج وإزالة السؤال.

6: قد أفتقد شيئًا ما ، ولكن إذا لم يتم طرح استثناء ، فما الذي يجب أن يفعله Peek أو Dequeue إذا كانت قائمة الانتظار فارغة؟ أفترض أن هذا بدأ في طرح السؤال حول ما إذا كان يجب أن تقبل بنية البيانات null (لا أفضل ذلك ، لكن ليس لدي رأي ثابت). حتى إذا سمحنا باستخدام null ، فإن أنواع القيم لا تحتوي على null (لا يتم احتساب default بالتأكيد) ، لذلك فإن Peek و Dequeue لا توجد وسيلة لنقل نتيجة لا معنى لها ، وأعتقد أنه يجب على النحو الواجب طرح استثناء (وبالتالي إزالة أي مخاوف بشأن معلمات out !). لا أرى أي سبب لعدم اتباع مثال Queue.Dequeue

سأضيف فقط أن Dijkstra (المعروف أيضًا باسم البحث التجريبي بدون توجيه) لا ينبغي أن يتطلب أي تغييرات في الأولوية. لم أرغب مطلقًا في تحديث أولوية أي شيء ، وأنا أقوم باستبعاد عمليات البحث التجريبية طوال الوقت. (الهدف من الخوارزمية هو أنه بمجرد استكشاف حالة ما ، فأنت تعلم أنك قد استكشفت أفضل طريق لها ، وإلا فإنها تخاطر بأن تكون غير مثالية ، وبالتالي لا يمكنك أبدًا تحسين الأولوية ، وبالتأكيد لا تريد تقليله (على سبيل المثال ، ضع في اعتبارك طريقًا أسوأ للوصول إليه). _ تجاهل الحالات التي تختار فيها عن قصد الاستدلال بحيث لا يكون هو الأمثل ، وفي هذه الحالة يمكنك بالطبع الحصول على نتيجة غير مثالية ، ولكنك لن تظل أبدًا تحديث أولوية_)

إذا لم يتم طرح استثناء ، فما الذي يجب أن تفعله Peek أو Dequeue إذا كانت قائمة الانتظار فارغة؟

حقيقي.

وبالتالي إزالة أي مخاوف بشأن المعلمات!

كيف؟ حسنًا ، المعلمات out مخصصة TryPeek و TryDequeue . تلك التي تطرح استثناء هي Peek و Dequeue .

سأضيف فقط أن Dijkstra's لا ينبغي أن تتطلب أي تغييرات في الأولوية.

قد أكون مخطئًا ، لكن على حد علمي ، تستخدم خوارزمية Dijkstra العملية DecreaseKey . انظر على سبيل المثال هذا . سواء كان هذا فعالًا أم لا ، فهذا جانب مختلف. في الواقع ، تم تصميم كومة فيبوناتشي بطريقة تحقق عملية DecreaseKey بشكل مقارب في O (1) (لتحسين Dijkstra).

ولكن لا يزال ، ما هو مهم في مناقشتنا - القدرة على تحديث عنصر في قائمة انتظار الأولوية أمر مفيد للغاية وهناك أشخاص يبحثون عن هذه الوظيفة (انظر الأسئلة المرتبطة سابقًا على StackOverflow). لقد استخدمتها بنفسي عدة مرات أيضًا.

عذرًا ، نعم ، أرى مشكلة مع معلمات out الآن ، أخطأت في قراءة الأشياء مرة أخرى. ويبدو أنه يمكن تنفيذ أحد أشكال Dijkstra's (اسم يبدو أنه مطبق على نطاق أوسع مما كنت أعتقد ...) بشكل أكثر كفاءة (إذا كانت قائمة الانتظار الخاصة بك تحتوي بالفعل على العناصر ، فمن المفترض أن يكون هناك فائدة لعمليات البحث القابلة للتكرار) باستخدام أولويات قابلة للتحديث. هذا ما أحصل عليه من استخدام كلمات وأسماء طويلة. لاحظ أنني لا أقترح أن نتخلى عن Update ، سيكون من الجيد أيضًا أن يكون لديك أقل من Heap أو غير ذلك (على سبيل المثال ، الاقتراح الأصلي) دون القيود التي تفرضها هذه الإمكانية (كما هي مجيدة قدرة كما أقدرها).

7: يجب أن تكون هذه out . لا يمكن أن يكون لأي إدخال أي معنى على الإطلاق ، لذلك لا يجب أن يكون موجودًا (على سبيل المثال ، لا يجب استخدام ref ). مع معلمات out ، لن نقوم بتحسين إعادة قيم default . هذا ما يفعله Dictionary.TryGetValue ، ولا أرى أي سبب للقيام بخلاف ذلك. _ ومع ذلك ، يمكنك التعامل مع ref كقيمة أو تقصير ، ولكن إذا لم يكن لديك تقصير ذو مغزى ، فهذا يحبط الأمور ._

مناقشة مراجعة API:

  • سنحتاج إلى اجتماع تصميم مناسب (ساعتان) لمناقشة جميع الإيجابيات / السلبيات ، لم تكن الـ 30 دقيقة التي استثمرناها اليوم كافية للوصول إلى إجماع / إغلاق حول الأسئلة المفتوحة.

    • من المحتمل أن ندعو معظم أعضاء المجتمع المستثمر -pgolebiowski ، أي شخص آخر؟

فيما يلي الملاحظات الأولية الأساسية:

  • التجربة - يجب أن نجري التجربة (في CoreFxLabs) ، ونطلقها كحزمة NuGet قبل الإصدار واطلب من المستهلكين الحصول على تعليقات (عبر منشور المدونة). لا نعتقد أنه يمكننا تثبيت واجهة برمجة التطبيقات بدون حلقة التغذية الراجعة.

    • لقد فعلنا شيئًا مشابهًا مقابل ImmutableCollections في الماضي (كانت دورة إصدار المعاينة السريعة أساسية ومفيدة في تشكيل واجهات برمجة التطبيقات).

    • يمكننا تجميع هذه التجربة مع MultiValueDictionary بالفعل في CoreFxLab. TODO: تحقق مما إذا كان لدينا المزيد من المرشحين ، فنحن لا نريد نشر مدونة لكل منهم على حدة.

    • سيتم تفجير حزمة NuGet التجريبية بعد انتهاء التجربة وسننقل الكود المصدري إلى CoreFX أو CoreFXExtensions (سيتم تحديده لاحقًا).

  • الفكرة: قم بإرجاع TElement من Peek & Dequeue ، لا تُرجع TPriority (يمكن للمستخدمين استخدام طرق Try* لذلك).
  • الاستقرار (للأولويات نفسها) - يجب إرجاع العناصر التي لها نفس الأولويات بالترتيب الذي تم إدخالها به في قائمة الانتظار (سلوك قائمة الانتظار العام)
  • نريد تمكين التكرارات (على غرار المجموعات الأخرى):

    • تخلص من Update - يمكن للمستخدم Remove ثم إرجاع العنصر Enqueue بأولوية مختلفة.

    • يجب أن يزيل Remove العنصر الأول الذي تم العثور عليه فقط (كما يفعل List ).

  • الفكرة: هل يمكننا تجريد واجهة IQueue لتجريد Queue و PriorityQueue ( Peek و Dequeue إرجاع TElement مساعدة هنا)

    • ملاحظة: قد يكون ذلك مستحيلًا ، لكن يجب علينا استكشافه قبل الاستقرار على واجهة برمجة التطبيقات

لقد أجرينا نقاشًا طويلًا حول المحدِّد - ما زلنا لم نقرر (قد يكون مطلوبًا بمقدار IQueue أعلاه؟). لم تحب الأغلبية المحدد ، ولكن قد تتغير الأشياء في اجتماع مراجعة API المستقبلي.

لم تتم مناقشة الأسئلة المفتوحة الأخرى.

يبدو الاقتراح الأخير جيدًا بالنسبة لي. آرائي / أسئلتي:

  1. إذا استخدم Peek و Dequeue معلمات out لـ priority ، فقد يكون لديهم أيضًا أحمال زائدة لا تُرجع الأولوية على الإطلاق ، وهو ما أعتقد أنه سيُبسط استعمال شائع. على الرغم من أنه يمكن أيضًا تجاهل الأولوية باستخدام التخلص ، مما يجعل هذا الأمر أقل أهمية.
  2. لا أحب أن يتم تمييز إصدار المحدد باستخدام مُنشئ مختلف وتمكين مجموعة مختلفة من الأساليب. ربما يجب أن يكون هناك PriorityQueue<T> منفصل؟ أو مجموعة من الأساليب الثابتة والإضافية تعمل مع PriorityQueue<T, T> ؟
  3. هل تحدد بأي ترتيب يتم إرجاع العناصر عند العد؟ أعتقد أنه غير محدد ، لجعله فعالاً.
  4. هل يجب أن تكون هناك طريقة ما لتعداد العناصر فقط في قائمة انتظار الأولوية ، مع تجاهل الأولويات؟ أم أن الحاجة إلى استخدام شيء مثل priorityQueue.Select(t => t.element) مقبول؟
  5. إذا كانت قائمة انتظار الأولوية تستخدم داخليًا Dictionary في نوع العنصر ، فهل يجب أن يكون هناك خيار لتمرير IEqualityComparer<TElement> ؟
  6. يعد تتبع العناصر باستخدام Dictionary نفقات إضافية غير ضرورية إذا لم أكن بحاجة إلى تحديث الأولويات. هل يجب أن يكون هناك خيار لتعطيله؟ على الرغم من أنه يمكن إضافة هذا لاحقًا ، إذا اتضح أنه مفيد.

تضمين التغريدة

الاستقرار (للأولويات نفسها) - يجب إرجاع العناصر التي لها نفس الأولويات بالترتيب الذي تم إدخالها به في قائمة الانتظار (سلوك قائمة الانتظار العام)

أعتقد أن هذا يعني أنه داخليًا ، يجب أن تكون الأولوية شيئًا مثل زوج من (priority, version) ، حيث يتم زيادة version لكل إضافة. أعتقد أن هذا سيؤدي إلى زيادة استخدام الذاكرة بشكل كبير في قائمة انتظار الأولوية ، خاصة بالنظر إلى أن version المحتمل أن يكون 64 بت. لست متأكدًا من أن ذلك يستحق ذلك.

لا نريد منع تكرار القيم (مثل المجموعات الأخرى):
تخلص من Update - يمكن للمستخدم Remove ثم استرجاع العنصر Enqueue بأولوية مختلفة.

اعتمادًا على التنفيذ ، من المحتمل أن يكون Update أكثر كفاءة من Remove متبوعًا بـ Enqueue . على سبيل المثال ، مع الكومة الثنائية ( أعتقد أن الكومة الرباعية لها نفس التعقيدات الزمنية) ، يكون Update (بقيم فريدة وقاموس) هو O (سجل n ) ، بينما Remove (بقيم مكررة ولا يوجد قاموس) هو O ( n ).

تضمين التغريدة
أوافق تمامًا على أننا بحاجة إلى الاستمرار في التركيز على المقترحات هنا ؛ لقد قدمت اقتراح API الأصلي والمنشور الأصلي. مرة أخرى في كانون الثاني (يناير) ، طلبت @ Karelz بعض أمثلة الاستخدام المحددة ؛ أدى إلى فتح سؤال أوسع نطاقاً فيما يتعلق بالحاجة المحددة واستخدام واجهة برمجة تطبيقات PriorityQueue. كان لدي تطبيق PriorityQueue الخاص بي واستخدمته في عدد قليل من المشاريع وشعرت أن شيئًا مشابهًا سيكون مفيدًا في BCL.

ما افتقرت إليه في المنشور الأصلي كان مسحًا واسعًا لاستخدام قائمة الانتظار الحالية ؛ ستساعد أمثلة العالم الحقيقي في الحفاظ على أساس التصميم وضمان إمكانية استخدامه على نطاق واسع.

تضمين التغريدة
يحتوي Lucene.net على وظيفة مقارنة قائمة انتظار ذات أولوية واحدة على الأقل (على غرار المقارنة) يقوم بتقييم حقول متعددة لتحديد الأولوية. لن يتم تعيين نمط المقارنة "الضمني" الخاص بـ Lucene بشكل جيد مع معامل TPriority الصريح لمقترح API الحالي. قد تكون هناك حاجة إلى نوع من التعيين - دمج الحقول المتعددة في واحد أو في بنية بيانات "قابلة للمقارنة" يمكن تمريرها على أنها أولوية TP.

عرض:
1) PriorityQueueفئة بناءً على اقتراحي الأصلي أعلاه (مدرج تحت عنوان الاقتراح الأصلي). يحتمل إضافة Update (T) ، و Remove (T) ، و يحتوي على وظائف الراحة (T). يناسب غالبية أمثلة الاستخدام الحالية من مجتمع مفتوح المصدر.
2) PriorityQueueمتغير dotnet / corefx # 1. Dequeue () و Peek () يعيدان TElement بدلاً من tuple. لا جرب * وظائف ، إزالة () ترجع منطقيًا بدلاً من رميها لتناسب القائمةنمط. يعمل "كنوع ملائم" لذلك لا يحتاج المطورون الذين لديهم قيمة أولوية صريحة إلى إنشاء نوع قابل للمقارنة خاص بهم.

كلا النوعين يدعمان العناصر المكررة. الحاجة إلى تحديد ما إذا كنا نضمن أو لا نضمن الوارد أولاً يصرف أولاً (FIFO) لعناصر من نفس الأولوية ؛ على الأرجح لا إذا دعمنا التحديثات ذات الأولوية.

قم ببناء كلا المتغيرين في CoreFxLabs كما اقترح @ Karelz واطلب الملاحظات.

أسئلة:

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)

لماذا لا يوجد تكرارات؟

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

إذا كانت هذه الطرق مرغوبة ، فهل لها كطرق امتداد؟

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

يجب رمي ولكن لماذا لا تجرب المتغير ، وكذلك طرق الامتداد؟

العداد القائم على الهيكل؟

تم اقتراحbenaadams رمي على المغفلين أصلاً (من قبلي) لتجنب نوع المقبض القبيح. آخر تحديث من مراجعة API يعيد ذلك.

يجب ألا نضيف طرقًا كطرق امتداد عندما نتمكن من إضافتها على النوع. تعتبر طرق الامتداد نسخًا احتياطيًا إذا لم نتمكن من إضافتها على النوع (على سبيل المثال ، إنها واجهة ، أو نريد تسليمها إلى .NET Framework بشكل أسرع).

ازدواجية: إذا كان لديك عدة إدخالات متشابهة ، فأنت تحتاج فقط إلى تحديث واحد؟ ثم يصبحون مختلفين؟

طرق الرمي: هي في الأساس أغلفة وستكون كذلك؟

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

رغم أن سيطرتها تتدفق عبر الاستثناءات؟

benaadams تحقق من https://github.com/dotnet/corefx/issues/574#issuecomment -308206064

  • نريد تمكين التكرارات (على غرار المجموعات الأخرى):

    • تخلص من Update - يمكن للمستخدم Remove ثم استرجاع العنصر Enqueue بأولوية مختلفة.

    • يجب أن يزيل Remove العنصر الأول الذي تم العثور عليه فقط (كما يفعل List ).

رغم أن سيطرتها تتدفق عبر الاستثناءات؟

لست متأكدا مما تقصده. يبدو أنه نمط شائع جدًا في BCL.

رغم أن سيطرتها تتدفق عبر الاستثناءات؟

لست متأكدا مما تقصده.

إذا كان لديك فقط طرق TryX

  • إذا كنت تهتم بالنتيجة ؛ قمت بفحصه
  • إذا كنت لا تهتم بالنتيجة ، فلا تتحقق منها

لا استثناء المشاركة ؛ لكن الاسم يشجعك على اتخاذ قرار بالتخلص من العائد.

إذا لم يكن لديك طرق رمي استثناءات لا تحاول

  • إذا كنت تهتم بالنتيجة ؛ عليك إما أن تحقق مسبقًا أو تستخدم المحاولة / الصيد للكشف
  • إذا كنت لا تهتم بالنتيجة ؛ عليك إما إفراغ طريقة catch ، أو الحصول على استثناءات غير متوقعة

لذا فأنت في نمط مضاد لاستخدام معالجة الاستثناءات للتحكم في التدفق . تبدو غير ضرورية بعض الشيء ؛ يمكن دائمًا إضافة رمية إذا كانت خاطئة لإعادة إنشائها.

يبدو أنه نمط شائع جدًا في BCL.

لم تكن طرق TryX نمطًا شائعًا في BCL الأصلي ؛ حتى الأنواع المتزامنة (على الرغم من التفكير في أن Dictionary.TryGetValue في الإصدار 2.0 ربما كان المثال الأول؟)

على سبيل المثال ، تمت إضافة طرق TryX للتو إلى Core for Queue و Stack وليست جزءًا من Framework حتى الآن.

استقرار

  • الاستقرار لا يأتي بالمجان. يأتي هذا مع زيادة في الأداء والذاكرة. للاستخدام العادي ، لا يعد الاستقرار في قوائم انتظار الأولوية أمرًا مهمًا.
  • نكشف عن IComparer . إذا أراد العميل الاستقرار ، فيمكنه بسهولة إضافته بأنفسهم. سيكون من السهل إنشاء StablePriorityQueue فوق تطبيقنا - باستخدام الأسلوب العادي في تخزين "عمر" عنصر ما واستخدامه أثناء المقارنات.

IQueue

ثم هناك تعارضات API. دعنا الآن نفكر في IQueue<T> بسيط بحيث يعمل مع 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);
}

لدينا خياران لقائمة انتظار الأولوية:

  1. تنفيذ IQueue<TElement> .
  2. تنفيذ IQueue<(TElement, TPriority)> .

سأكتب مضامين لكلا المسارين باستخدام الأرقام (1) و (2).

قائمة الانتظار

في قائمة انتظار الأولوية ، نحتاج إلى إدراج كل من العنصر وأولويته (الحل الحالي). في الكومة العادية ، نضيف عنصرًا واحدًا فقط.

  1. في الحالة الأولى ، نكشف عن Enqueue(TElement) ، وهو أمر غريب جدًا بالنسبة لقائمة انتظار ذات أولوية إذا كنا ندرج عنصرًا بدون أولوية. ثم نحن مجبرون على القيام ... ماذا؟ افترض default(TPriority) ؟ ناه ...
  2. في الحالة الثانية ، نكشف عن Enqueue((TElement, TPriority) element) . نضيف أساسًا وسيطين من خلال قبول مجموعة تم إنشاؤها منها. ربما لا تكون API مثالية.

نظرة خاطفة و Dequeue

أردت هذه الأساليب لإرجاع TElement فقط.

  1. يعمل مع ما تريد تحقيقه.
  2. لا يعمل مع ما تريد تحقيقه - إرجاع (TElement, TPriority) .

عد

  1. لا يمكن للعميل كتابة أي استعلام LINQ يستخدم أولويات العناصر. هذا خطأ.
  2. إنها تعمل.

يمكننا التخفيف من بعض المشكلات من خلال توفير PriorityQueue<T> ، حيث لا توجد أولوية فعلية منفصلة.

  • يضطر المستخدم أساسًا إلى كتابة فئة مجمعة حتى يتمكن رمزه من استخدام فصلنا. هذا يعني أنه في كثير من الحالات قد يرغبون أيضًا في تنفيذ IComparable . مما يضيف الكثير من التعليمات البرمجية المعيارية إلى حد ما (وملف جديد في شفرة المصدر الخاصة بهم ، على الأرجح).
  • من الواضح أنه يمكننا إعادة مناقشة هذا ، إذا كنت تريد ذلك. أنا أيضا أقدم نهجا بديلا أدناه.

اثنان من قوائم الانتظار ذات الأولوية

إذا قدمنا ​​قائمتين من قوائم الانتظار ذات الأولوية ، فسيكون الحل العام أكثر قوة ومرونة. هناك بعض الملاحظات التي يجب تدوينها:

  • نعرض فئتين لتوفير نفس الوظيفة ، ولكن فقط لأنواع مختلفة من تنسيق الإدخال. لا أشعر بتحسن.
  • PriorityQueue<T> المحتمل أن يقوم IQueue<T> .
  • سيكشف PriorityQueue<TElement, TPriority> واجهة برمجة تطبيقات غريبة إذا حاولت تنفيذ واجهة IQueue .

حسنًا ... سيعمل . على الرغم من أنه ليس مثاليًا.

التحديث والإزالة

ونظرا لافتراض أننا نريد للسماح التكرارات، ونحن لا نريد أن استخدام مفهوم مقبض:

  • من المستحيل توفير وظيفة تحديث أولويات العناصر بشكل كامل (تحديث العقدة المحددة فقط). بشكل مماثل ، فإنه ينطبق على إزالة العناصر.
  • بالإضافة إلى ذلك ، يجب إجراء كلتا العمليتين في O (n) ، وهو أمر محزن للغاية ، لأنه من الممكن القيام بكلتا العمليتين في O (log n).

يؤدي هذا بشكل أساسي إلى ما هو موجود في Java ، حيث يتعين على المستخدمين توفير التنفيذ الخاص بهم لهيكل البيانات بالكامل إذا كانوا يريدون أن يكونوا قادرين على تحديث الأولويات في قوائم انتظار الأولوية الخاصة بهم دون التكرار بسذاجة من خلال المجموعة بأكملها في كل مرة.

مقبض بديل

مع افتراض أننا لا نريد إضافة نوع مقبض جديد ، يمكننا القيام بذلك بشكل مختلف حتى نتمكن من الحصول على الدعم الكامل لتحديث العناصر وإزالتها (بالإضافة إلى القيام بذلك بكفاءة). الحل هو إضافة طرق بديلة:

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

void Update(object handle, TPriority priority);

void Remove(object handle);

ستكون هذه طرقًا لأولئك الذين يرغبون في الحصول على تحكم مناسب في تحديث وإزالة العناصر (ولا تفعل ذلك في O (n) ، كما لو كان List : stuck_out_tongue_winking_eye :).

لكن الأفضل على الرغم من ذلك ، دعنا نفكر ...

طريقه بديله

بدلاً من ذلك ، يمكننا إزالة هذه الوظيفة من قائمة انتظار الأولوية بالكامل وإضافتها في الكومة بدلاً من ذلك. هذا له فوائد عديدة:

  • تتوفر العمليات الأكثر قوة وكفاءة باستخدام Heap .
  • تتوفر واجهة برمجة تطبيقات بسيطة واستخدام مباشر باستخدام PriorityQueue - للأشخاص الذين يريدون أن تعمل التعليمات البرمجية الخاصة بهم
  • نحن لا نواجه مشاكل جافا.
  • يمكننا أن نجعل PriorityQueue مستقرًا الآن - لم تعد مقايضة بعد الآن.
  • يتوافق الحل مع الشعور بأن الأشخاص الذين لديهم خلفية أقوى في علوم الكمبيوتر سيكونون على دراية بوجود Heap . سيكونون أيضًا على دراية بحدود PriorityQueue وبالتالي يمكنهم استخدام Heap بدلاً من ذلك (على سبيل المثال إذا كانوا يريدون الحصول على مزيد من التحكم في تحديث / إزالة العناصر أو لا يريدون البيانات أن تكون البنية مستقرة على حساب السرعة واستخدام الذاكرة).
  • يمكن أن تسمح كل من قائمة انتظار الأولوية وكومة الذاكرة المؤقتة بسهولة بالتكرارات مع عدم المساومة على وظائفها (لأن أغراضها مختلفة).
  • سيكون من الأسهل إنشاء واجهة مشتركة IQueue - لأنه سيتم طرح الطاقة والوظائف في حقل الكومة. يمكن أن تركز واجهة برمجة التطبيقات PriorityQueue على جعلها متوافقة مع Queue خلال عملية تجريد.
  • لا نحتاج إلى توفير واجهة IPriorityQueue بعد الآن (نركز بدلاً من ذلك على الحفاظ على وظائف PriorityQueue و Queue مماثلة). بدلاً من ذلك ، يمكننا إضافته في منطقة الكومة - ولدينا أساسًا IHeap هناك. يعد هذا أمرًا رائعًا ، لأنه يتيح للأشخاص إنشاء مكتبات تابعة لجهات خارجية فوق ما هو موجود في المكتبة القياسية. وهذا يبدو صحيحًا - لأننا مرة أخرى ، نعتبر الأكوام أكثر تقدمًا من قوائم الانتظار ذات الأولوية ، لذلك سيتم توفير الامتدادات من خلال تلك المنطقة. لن تعاني هذه الإضافات أيضًا من الخيارات التي قد نقوم بها في PriorityQueue ، لأنها ستكون منفصلة.
  • لا نحتاج إلى اعتبار المُنشئ IHeap لـ PriorityQueue بعد الآن.
  • قد تكون قائمة انتظار الأولوية فئة مفيدة للاستخدام داخليًا في CoreFX. ومع ذلك ، إذا أضفنا ميزات مثل الاستقرار وإسقاط بعض الوظائف الأخرى ، فمن المحتمل أن ينتهي بنا الأمر بالحاجة إلى شيء أقوى من هذا الحل. لحسن الحظ ، سيكون هناك Heap الأقوى والأداء بأمرنا!

بشكل أساسي ، ستركز قائمة الانتظار ذات الأولوية بشكل أساسي على سهولة الاستخدام ، على حساب: الأداء والقوة والمرونة. سيكون الكومة لأولئك الذين هم على دراية بالأداء والآثار الوظيفية لقراراتنا في قائمة انتظار الأولوية. نحن نخفف العديد من المشكلات المتعلقة بالمقايضات.

إذا أردنا إجراء تجربة ، أعتقد أن ذلك ممكن الآن. دعنا فقط نسأل المجتمع. لا يقرأ الجميع هذا الموضوع - قد ننشئ تعليقات قيمة أخرى وسيناريوهات الاستخدام. ماذا تعتقد؟ أنا شخصياً أحب مثل هذا الحل. أعتقد أنه سيجعل الجميع سعداء.

ملاحظة مهمة: إذا أردنا مثل هذا النهج ، فنحن بحاجة إلى تصميم قائمة انتظار ذات أولوية وكومة معًا . لأن أغراضهم ستكون مختلفة وسيوفر أحد الحلول ما لا يوفره الآخر.

إيصال IQueue بعد ذلك

باستخدام النهج الموضح أعلاه ، من أجل تنفيذ قائمة انتظار الأولوية IQueue<T> (بحيث يكون ذلك منطقيًا) ، وبافتراض أننا نتخلى عن دعم المحدد هناك ، يجب أن يكون لها نوع عام واحد. على الرغم من أن هذا قد يعني أن المستخدمين بحاجة إلى توفير غلاف إذا كان لديهم (user data, priority) منفصل ، إلا أن هذا الحل لا يزال بديهيًا. والأهم من ذلك ، أنه يسمح بجميع تنسيقات الإدخال (وهذا هو السبب في أنه يجب القيام به بهذه الطريقة إذا أسقطنا المحدد). بدون المحدد ، لن يسمح Enqueue(TElement, TPriority) بالأنواع التي يمكن مقارنتها بالفعل. نوع عام واحد مهم أيضًا للتعداد - بحيث يمكن تضمين هذه الطريقة في IQueue<T> .

متنوع

svick

هل تحدد بأي ترتيب يتم إرجاع العناصر عند العد؟ أعتقد أنه غير محدد ، لجعله فعالاً.

للحصول على الترتيب أثناء العد ، نحتاج أساسًا إلى فرز المجموعة. لهذا السبب نعم ، يجب أن يكون غير محدد ، حيث يمكن للعميل ببساطة تشغيل OrderBy بنفسه وتحقيق نفس الأداء (لكن بعض الناس لا يحتاجون إليه).

الفكرة: في قائمة انتظار الأولوية يمكن ترتيب ذلك ، في كومة الذاكرة المؤقتة غير مرتبة. إنه شعور أفضل. بطريقة ما تبدو قائمة انتظار الأولوية وكأنها تكررها بالترتيب. كومة بالتأكيد لا. فائدة أخرى للنهج أعلاه.

تضمين التغريدة
كل هذا يبدو عاقل جدا. هل تمانع في التوضيح ، في قسم Delivering IQueue then ، هل تقترح T : IComparable<T> لـ "نوع عام واحد" كبديل للمحدد بالنظر إلى السماح بالعناصر المكررة؟

سأدعم وجود النوعين المنفصلين.

لا أفهم الأساس المنطقي وراء استخدام object كنوع المقبض: هل هذا فقط لتجنب إنشاء نوع جديد؟ سيوفر تحديد نوع جديد الحد الأدنى من تكاليف التنفيذ ، مع جعل إساءة استخدام واجهة برمجة التطبيقات (API) أكثر صعوبة (ما الذي يمنعني من محاولة تمرير string إلى Remove(object) ؟) ، وأسهل في الاستخدام (ما الذي يمنعني من المحاولة لتمرير العنصر نفسه إلى Remove(object) ، ومن يمكن أن يلومني على المحاولة؟).

أقترح إضافة النوع الوهمي المسمى بشكل مناسب ، لاستبدال object في طرق المقبض ، لصالح واجهة أكثر تعبيرًا.

إذا كانت قابلية تصحيح الأخطاء تتفوق على حمل الذاكرة ، فقد يتضمن نوع المقبض معلومات حول قائمة الانتظار التي ينتمي إليها (يجب أن تصبح عامة ، مما سيعزز أمان النوع للواجهة) ، مما يوفر استثناءات مفيدة على طول سطور ("تم إنشاء المقبض الموفر بواسطة قائمة انتظار مختلفة ") ، أو ما إذا كان قد تم استهلاكه بالفعل (" العنصر المشار إليه بواسطة المؤشر تمت إزالته بالفعل ").

إذا تم المضي قدمًا في فكرة المقبض ، أقترح أنه إذا تم اعتبار هذه المعلومات مفيدة ، فسيتم أيضًا طرح مجموعة فرعية من هذه الاستثناءات من خلال الطرق المقابلة TryRemove و TryUpdate ، باستثناء ذلك حيث لم يعد العنصر موجودًا ، إما لأنه تم إلغاء صفحته أو إزالته بواسطة المقبض. قد يستلزم ذلك نوع مقبض أقل مللاً وعمومًا واسمًا مناسبًا.

تضمين التغريدة

هل تمانع في التوضيح ، في قسم Delivering IQueue then ، هل تقترح T : IComparable<T> لـ "نوع عام واحد" كبديل للمحدد بالنظر إلى السماح بالعناصر المكررة؟

آسف لعدم توضيح هذا.

  • قصدت تسليم PriorityQueue<T> ، بدون أي قيود على T .
  • سيستمر استخدام IComparer<T> .
  • إذا حدث أن T قابل للمقارنة بالفعل ، فببساطة سيتم افتراض Comparer<T>.Default (ويمكنك استدعاء المُنشئ الافتراضي لقائمة انتظار الأولوية بدون وسيطات).
  • كان للمُحدد غرض مختلف - وهو القدرة على استهلاك جميع أنواع بيانات المستخدم. هناك عدة تكوينات:

    1. بيانات المستخدم منفصلة عن الأولوية (حالتان ماديتان).
    2. بيانات المستخدم تحتوي على الأولوية.
    3. بيانات المستخدم هي الأولوية.
    4. حالة نادرة: يمكن الحصول على الأولوية من خلال منطق آخر (توجد في كائن مختلف عن بيانات المستخدم).

    لن تكون الحالة النادرة ممكنة في PriorityQueue<T> ، لكن هذا لا يهم كثيرًا. المهم أنه يمكننا الآن التعامل مع (1) و (2) و (3). ومع ذلك ، إذا كان لدينا نوعان عامان ، فسيتعين علينا الحصول على طريقة مثل Enqueue(TElement, TPrioriity) . هذا سيقتصر على (1) فقط. (2) من شأنه أن يؤدي إلى التكرار. (3) ستكون قبيحة بشكل لا يصدق. هناك المزيد حول هذا في قسم IQueue > Enqueue أعلاه (الطريقة الثانية Enqueue و default(TPriority ).

آمل أن يكون أوضح الآن.

راجع للشغل ، بافتراض مثل هذا الحل ، فإن تصميم API PriorityQueue<T> و IQueue<T> سيكون تافهًا. فقط خذ بعض الطرق في Queue<T> ، قم برميها في IQueue<T> واجعل PriorityQueue<T> يطبقونها. تداع! 😄

لا أفهم الأساس المنطقي لاستخدام object كنوع المقبض: هل هذا فقط لتجنب إنشاء نوع جديد؟

  • نعم بالضبط. بالنظر إلى الافتراض بأننا لا نريد كشف مثل هذا النوع ، فهو الحل الوحيد الذي لا يزال يستخدم مفهوم المقبض (وعلى هذا النحو يتمتع بمزيد من القوة والسرعة). وأنا أتفق انها ليست مثالية على الرغم - كان بالأحرى توضيح ما يجب أن يحدث إذا تمسكنا بالنهج الحالي وترغب في الحصول على مزيد من القوة والكفاءة (وأنا ضد).
  • بالنظر إلى أننا سوف نتبع النهج البديل (قائمة انتظار ذات أولوية منفصلة وأكوام) ، فإن هذا أسهل. يمكننا ترك PriorityQueue<T> موجودًا في System.Collections.Generic ، بينما ستكون وظيفة الكومة في System.Collections.Specialized . هناك ، ستكون لدينا فرصة أكبر لتقديم مثل هذا النوع ، وفي النهاية يكون لدينا اكتشاف خطأ وقت الترجمة الرائع.
  • ولكن مرة أخرى - من المهم جدًا تصميم وظائف قائمة الانتظار ووظائف الكومة ذات الأولوية معًا إذا كنا نرغب في الحصول على مثل هذا النهج. لأن أحد الحلول يوفر ما لا يوفره الآخر.

إذا كانت قابلية تصحيح الأخطاء تتفوق على حمل الذاكرة ، فقد يتضمن نوع المقبض معلومات حول قائمة الانتظار التي ينتمي إليها (يجب أن تصبح عامة ، مما سيعزز أمان النوع للواجهة) ، مما يوفر استثناءات مفيدة على طول سطور ("تم إنشاء المقبض الموفر بواسطة قائمة انتظار مختلفة ") ، أو ما إذا كان قد تم استهلاكه بالفعل (" العنصر المشار إليه بواسطة المؤشر تمت إزالته بالفعل ").

إذا استمرت فكرة المقبض ، أقترح أنه إذا تم اعتبار هذه المعلومات مفيدة ...

بالتأكيد أكثر ممكن بعد ذلك: غمزة :. خاصة لتوسيع كل ذلك من قبل مجتمعنا في مكتبات الطرف الثالث.

karelzsafernianhaysterrajobstbendonosvick @ أليكسي-dvortsovSamuelEnglard @ xied75 وغيرها - هل تعتقد أن مثل هذا النهج معنى (كما هو موضح في هذا و هذا آخر)؟ هل ستلبي جميع توقعاتك واحتياجاتك؟

أعتقد أن فكرة إنشاء فصلين منطقية جدًا وتحل الكثير من المشكلات. على الرغم فقط لدي هو أنه قد يكون من المنطقي ل PriorityQueue<T> لاستخدام Heap<T> داخليا وفقط "الاختباء" انها وظائف متقدمة.

لذلك في الأساس ...

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

ملحوظات

  • في System.Collections.Generic .
  • الفكرة: يمكننا أيضًا إضافة طرق لإزالة العناصر هنا ( Remove و TryRemove ). ليس في Queue<T> رغم ذلك. لكنها ليست ضرورية.

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

ملحوظات

  • في System.Collections.Generic .
  • إذا كان IComparer<T> لا يتم تسليم، Comparer<T>.Default ما استدعى.
  • إنه مستقر.
  • يسمح بالتكرارات.
  • Remove و TryRemove قم بإزالة التواجد الأول فقط (إذا وجدوه).
  • أمر العد.

أكوام

لا أكتب كل شيء هنا الآن ، ولكن:

  • في System.Collections.Specialized .
  • لن يكون مستقرًا (وعلى هذا النحو أسرع وأكثر كفاءة من حيث الذاكرة).
  • التعامل مع الدعم والتحديث والإزالة المناسبة

    • يتم القيام به بسرعة في O (تسجيل الدخول) بدلاً من O (n)

    • بشكل صحيح

  • لم يتم ترتيب العد (أسرع).
  • يسمح بالتكرارات.

أتفق مع IQueueاقتراح. كنت سأقوم بعرض الشيء نفسه اليوم ، يبدو أنه المستوى الصحيح من التجريد على مستوى الواجهة. "واجهة لهيكل بيانات يمكن أن تتم إضافة عناصر إليه وإزالة العناصر المطلوبة منه."

  • مواصفات IQueueتبدو جيدا.

  • ضع في اعتبارك إضافة "int Count {get؛}" إلى IQueueبحيث يكون من الواضح أن Count هو المطلوب بغض النظر عما إذا كنا نرث من IReadOnlyCollection أم لا.

  • على السياج بخصوص TryPeek ، TryDequeue في IQueueمعتبرا أنهم ليسوا في قائمة الانتظار، ولكن ربما ينبغي إضافة هؤلاء المساعدين إلى Queue و Stack أيضًا.

  • IsEmpty يبدو وكأنه غريب ؛ ليس لدى العديد من أنواع المجموعات الأخرى في BCL. لإضافته إلى الواجهة ، يجب أن نفترض أنه سيتم إضافته إلى قائمة الانتظار، ويبدو غريبًا بعض الشيء إضافته إلى قائمة الانتظارولا شيء غير ذلك. نوصي بإسقاطه من الواجهة ، وربما الفصل أيضًا.

  • إسقاط TryRemove وتغيير "إزالة" إلى "bool Remove". ستكون المواءمة مع فئات المجموعات الأخرى مهمة هنا - سيكون لدى المطورين الكثير من الذاكرة العضلية التي تقول "إزالة () في مجموعة لا ترمي". إنها منطقة لن يختبرها العديد من المطورين جيدًا وستسبب الكثير من المفاجآت إذا تم تغيير السلوك العادي.

من الاقتباس السابق الخاص بك pgolebiowski

  1. بيانات المستخدم منفصلة عن الأولوية (حالتان ماديتان).
  2. بيانات المستخدم تحتوي على الأولوية.
  3. بيانات المستخدم هي الأولوية.
  4. حالة نادرة: يمكن الحصول على الأولوية من خلال منطق آخر (توجد في كائن مختلف عن بيانات المستخدم).

أوصي أيضًا بالنظر في 5. تحتوي بيانات المستخدم على الأولوية في عدة حقول (كما رأينا في Lucene.net)

على السياج المتعلق بـ TryPeek ، TryDequeue في IQueue معتبرا أنهم ليسوا في قائمة الانتظار

هم System / Collections / Generic / Queue.cs # L253-L295

من ناحية أخرى ، لا يوجد في قائمة الانتظار
c# public void Remove(T element); public bool TryRemove(T element);

ضع في اعتبارك إضافة "int Count {get؛}" إلى IQueue بحيث يكون من الواضح أن Count هو المطلوب بغض النظر عما إذا كنا نرث من IReadOnlyCollection أم لا.

نعم. سيتم تعديله.

IsEmpty يبدو وكأنه غريب ؛ ليس لدى العديد من أنواع المجموعات الأخرى في BCL.

تمت إضافة هذا بواسطة karelz ، لقد قمت بنسخه للتو. أنا أحب ذلك على الرغم من أنه قد يتم النظر فيه في مراجعة واجهة برمجة التطبيقات :)

إسقاط TryRemove وتغيير "إزالة" إلى "bool Remove".

أعتقد أن Remove و TryRemove يتوافق مع طرق أخرى مثل ( Peek و TryPeek أو Dequeue و TryDequeue ).

  1. تحتوي بيانات المستخدم على الأولوية في مجالات متعددة

إنها نقطة صالحة ، ولكن في الواقع يمكن أيضًا التعامل معها باستخدام محدد (إنها أي وظيفة بعد كل شيء) - ولكن هذا لأكوام.

IsEmpty يبدو وكأنه غريب ؛ ليس لدى العديد من أنواع المجموعات الأخرى في BCL.

FWIW ، bool IsEmpty { get; } هو شيء أتمنى لو أضفناه إلى IProducerConsumerCollection<T> ، وقد ندمت على عدم تواجدي في عدة مناسبات منذ ذلك الحين. بدونها ، غالبًا ما تحتاج الأغلفة إلى عمل ما يعادل Count == 0 ، والذي يعتبر بالنسبة لبعض المجموعات أقل كفاءة في التنفيذ ، لا سيما في معظم المجموعات المتزامنة.

pgolebiowski ما هو شعورك حيال فكرة إنشاء مستودع جيثب لاستضافة عقود API الحالية وملف .md أو اثنين يحتويان على الأساس المنطقي للتصميم. بمجرد أن يستقر ذلك ، يمكن استخدامه كمكان لبناء التنفيذ الأولي قبل القيام بعلاقات عامة تصل إلى CoreFxLabs عندما تكون جاهزًا؟

svick

إذا استخدم Peek و Dequeue معلمات out للأولوية ، فقد يكون لديهم أيضًا أحمال زائدة لا تُرجع الأولوية على الإطلاق ، وهو ما أعتقد أنه سيبسط الاستخدام الشائع

متفق. دعونا نضيفهم.

هل يجب أن تكون هناك طريقة ما لتعداد العناصر فقط في قائمة انتظار الأولوية ، مع تجاهل الأولويات؟

سؤال جيد. ماذا تقترح؟ IEnumerable<TElement> Elements { get; } ؟

الاستقرار - أعتقد أن هذا يعني أنه داخليًا ، يجب أن تكون الأولوية شيئًا مثل زوج من (priority, version)

أعتقد أنه يمكننا تجنب ذلك من خلال اعتبار Update منطقيًا Remove + Enqueue . سنضيف عناصر دائمًا إلى نهاية العناصر ذات الأولوية نفسها (ضع في اعتبارك أساسًا نتيجة المقارنة 0 كـ -1). يجب أن تعمل المنظمة البحرية الدولية.


تضمين التغريدة

لم تكن طرق TryX نمطًا شائعًا في BCL الأصلي ؛ حتى الأنواع المتزامنة (على الرغم من التفكير في أن Dictionary.TryGetValue في الإصدار 2.0 ربما كان المثال الأول؟)
على سبيل المثال ، تمت إضافة طرق TryX للتو إلى Core for Queue و Stack وليست جزءًا من Framework حتى الآن.

أعترف أنني ما زلت جديدًا في BCL. من اجتماعات مراجعة API ومن حقيقة أننا أضفنا مجموعة من الأساليب Try* مؤخرًا كان لدي انطباع بأنه نمط شائع لفترة أطول 😉.
في كلتا الحالتين ، إنه نمط شائع الآن ويجب ألا نخاف من استخدامه. حقيقة أن النمط ليس موجودًا في .NET Framework حتى الآن لا ينبغي أن يمنعنا من الابتكار في .NET Core - وهذا هو الغرض الرئيسي منه ، للابتكار بشكل أسرع.


تضمين التغريدة

بدلاً من ذلك ، يمكننا إزالة هذه الوظيفة من قائمة انتظار الأولوية بالكامل وإضافتها في الكومة بدلاً من ذلك. هذا له فوائد عديدة

حسنًا ، هناك شيء يخبرني أنه قد يكون لديك جدول أعمال هنا 😆
الآن على محمل الجد ، إنه في الواقع اتجاه جيد كنا نهدف إليه طوال الوقت - لم يكن من المفترض أن يكون PriorityQueue سببًا لعدم القيام بـ Heap . إذا كنا جميعًا على ما يرام مع حقيقة أن Heap قد لا يصل إلى CoreFX وقد يظل "فقط" في CoreFXExtensions repo كهيكل بيانات متقدم جنبًا إلى جنب مع PowerCollections ، فأنا موافق على ذلك ،

ملاحظة مهمة: إذا أردنا مثل هذا النهج ، فنحن بحاجة إلى تصميم قائمة انتظار ذات أولوية وكومة معًا. لأن أغراضهم ستكون مختلفة وسيوفر أحد الحلول ما لا يوفره الآخر.

لا أفهم لماذا نحتاج إلى القيام بذلك معًا. IMO يمكننا التركيز على PriorityQueue وإضافة "مناسب" Heap بالتوازي / لاحقًا. لا أمانع إذا قام أحدهم بعملها معًا ، لكنني لا أرى أي سبب قوي لوجود PriorityQueue سهل الاستخدام يجب أن يؤثر على تصميم "مناسب / عالي الأداء" المتقدم Heap عائلة.

IQueue

شكرا على الكتابة حتى. بالنظر إلى نقاطك ، لا أعتقد أنه يجب علينا بذل جهدنا لإضافة IQueue . IMO من الجيد امتلاكها. إذا كان لدينا محدد ، فسيكون ذلك طبيعيًا. ومع ذلك ، لا أحب نهج المحدد لأنه يجلب الغرابة والتعقيد في وصف وقت استدعاء المحدد بواسطة PriorityQueue (فقط عند Enqueue و Update .

مقبض (كائن) بديل

هذه في الواقع ليست فكرة سيئة (على الرغم من أنها قبيحة بعض الشيء) أن يكون لديك مثل هذه الأحمال الزائدة IMO. يجب أن نكون قادرين على اكتشاف أن المقبض من PriorityQueue خاطئ ، وهو O (log n).
لدي شعور بأن مراجعي API سيرفضونه ، لكن IMO يستحق التجربة لتجربته ...

استقرار

لا أعتقد أن الاستقرار يأتي مع أي زيادة في الأداء / الذاكرة (بافتراض أننا أسقطنا Update بالفعل أو أننا نتعامل مع Update أنه Remove + Enqueue ، لذلك قمنا بشكل أساسي بإعادة تعيين عمر العنصر). فقط تعامل مع النتيجة المقارنة 0 كـ -1 وكل شيء على ما يرام ... أم أنني أفتقد شيئًا ما؟

المحدد و IQueue<T>

قد تكون فكرة جيدة أن يكون لديك مقترحان (وقد نأخذ كلاهما):

  • PriorityQueue<T,U> بدون محدد وبدون IQueue (والذي سيكون مرهقًا)
  • PriorityQueue<T> مع المحدِّد و IQueue

ألمح عدد غير قليل من الناس إلى ذلك أعلاه.

رد: أحدث اقتراح من pgolebiowski - https://github.com/dotnet/corefx/issues/574#issuecomment -308427321

IQueue<T> - يمكننا إضافة Remove و TryRemove ، لكن Queue<T> لا يمتلكهما.

هل من المنطقي إضافتهم إلى Queue<T> ؟ ( ebickle يوافق) إذا كانت الإجابة بنعم ، فيجب علينا تجميع الإضافة أيضًا.
دعنا نضيفها في الواجهة ونوضح أنها مشكوك فيها / تحتاج إلى إضافة Queue<T> أيضًا.
نفس الشيء بالنسبة إلى IsEmpty - أيًا كان ما يحتاج إلى إضافات Queue<T> و Stack<T> ، فلنحدد ذلك في الواجهة (سيكون من الأسهل مراجعتها وهضمها).
pgolebiowski هل يمكنك إضافة تعليق إلى IQueue<T> مع قائمة الفئات التي نعتقد أنه سيتم تنفيذها بواسطة؟

PriorityQueue<T>

دعنا نضيف عدادًا هيكليًا (أعتقد أنه نمط BCL شائع مؤخرًا ). تم استدعاؤه عدة مرات على الخيط ، ثم تم إسقاطه / نسيانه.

أكوام

اختيار مساحة الاسم: دعونا لا نضيع الوقت في قرار مساحة الاسم حتى الآن. إذا انتهى الأمر بـ Heap في CoreFxExtensions ، فإننا لا نعرف حتى الآن نوع مساحات الأسماء التي سنسمح بها هناك. ربما Community.* أو شيء من هذا القبيل. يعتمد ذلك على نتيجة مناقشات وضع التشغيل / الغرض CoreFxExtensions.
ملاحظة: تتمثل إحدى الأفكار الخاصة بـ CoreFxExtensions repo في منح أعضاء المجتمع الذين أثبتوا صلاحياتهم للكتابة ، والسماح بدفعها بشكل أساسي من خلال المجتمع مع فريق .NET الذي يقدم المشورة فقط (بما في ذلك خبرة مراجعة واجهة برمجة التطبيقات) ويكون فريق .NET / MS هو الحكم عند الحاجة. إذا كان هذا هو المكان الذي وصلنا إليه ، فربما لا نريده في مساحة الاسم System.* أو Microsoft.* . (إخلاء المسئولية: التفكير المبكر ، من فضلك لا تقفز إلى الاستنتاجات بعد ، هناك أفكار حوكمة بديلة أخرى في الطريق)

قم بإسقاط TryRemove وتغيير Remove إلى bool Remove . ستكون المواءمة مع فئات المجموعات الأخرى مهمة هنا - سيكون لدى المطورين الكثير من الذاكرة العضلية التي تقول "إزالة () في مجموعة لا ترمي". إنها منطقة لن يختبرها العديد من المطورين جيدًا وستسبب الكثير من المفاجآت إذا تم تغيير السلوك العادي.

👍 يجب علينا بالتأكيد على الأقل التفكير في مواءمته مع المجموعات الأخرى. هل يمكن لأي شخص مسح المجموعات الأخرى ما هو نمط Remove بهم؟

ebickle ما هو شعورك حيال فكرة إنشاء مستودع جيثب لاستضافة عقود API الحالية وملف .md أو اثنين يحتوي على الأساس المنطقي للتصميم.

دعنا نستضيفه مباشرة في

تضمين التغريدة

هل يجب أن تكون هناك طريقة ما لتعداد العناصر فقط في قائمة انتظار الأولوية ، مع تجاهل الأولويات؟

سؤال جيد. ماذا تقترح؟ IEnumerable<TElement> Elements { get; } ؟

نعم ، إما ذلك أو خاصية إرجاع نوع من ElementsCollection هو الخيار الأفضل على الأرجح. خاصة أنه مشابه لـ Dictionary<K, V>.Values .

سنضيف عناصر دائمًا إلى نهاية العناصر ذات الأولوية نفسها (ضع في اعتبارك أساسًا نتيجة المقارنة 0 كـ -1). يجب أن تعمل المنظمة البحرية الدولية.

هذه ليست طريقة عمل الأكوام ، فلا يوجد "نهاية للعناصر ذات الأولوية نفسها". يمكن أن تنتشر العناصر التي لها نفس الأولوية في جميع أنحاء الكومة (ما لم تكن قريبة من الحد الأدنى الحالي).

أو ، بعبارة أخرى ، للحفاظ على الاستقرار ، عليك التفكير في نتيجة المقارنة من 0 إلى -1 ليس فقط عند الإدراج ، ولكن أيضًا عند تحريك العناصر في الكومة بعد ذلك. وأعتقد أنه يجب عليك تخزين شيء مثل version مع priority للقيام بذلك بشكل صحيح.

IQueue- يمكننا إضافة Remove و TryRemove ، لكن Queueليس لديهم.

هل من المنطقي إضافتهم إلى قائمة الانتظار؟ ( ebickle يوافق) إذا كانت الإجابة بنعم ، فيجب علينا تجميع الإضافة أيضًا.

لا أعتقد أنه يجب إضافة Remove إلى IQueue<T> ؛ وأنا أقترح حتى أن Contains مراوغ ؛ فهو يحد من فائدة الواجهة وأنواع قائمة الانتظار التي يمكن استخدامها من أجلها ما لم تبدأ أيضًا في طرح NotSupportedExceptions. أي ما هو النطاق؟

هل هي مخصصة فقط لقوائم انتظار الفانيليا وقوائم انتظار الرسائل وقوائم الانتظار الموزعة وقوائم انتظار ServiceBus وقوائم انتظار تخزين Azure وقوائم انتظار ServiceFabric الموثوق وما إلى ذلك ...

هذه ليست طريقة عمل الأكوام ، فلا يوجد "نهاية للعناصر ذات الأولوية نفسها". يمكن أن تنتشر العناصر التي لها نفس الأولوية في جميع أنحاء الكومة (ما لم تكن قريبة من الحد الأدنى الحالي).

نقطة عادلة. كنت أفكر في الأمر على أنه شجرة بحث ثنائية. يجب أن فرشاة أساسيات ds الخاصة بي على ما أعتقد :)
حسنًا ، إما أن ننفذها باستخدام ds مختلفة (مصفوفة ، شجرة بحث ثنائية (أو أيًا كان الاسم الرسمي) version أو age يستحق ضمان الاستقرار.

ebickle ما هو شعورك حيال فكرة إنشاء مستودع جيثب لاستضافة عقود API الحالية وملف .md أو اثنين يحتوي على الأساس المنطقي للتصميم.

karelz لنستضيفه مباشرة في CoreFxLabs.

يرجى إلقاء نظرة على هذه الوثيقة .

قد تكون فكرة جيدة أن يكون لديك مقترحان (وقد نأخذ كلاهما):

  • طابور الأولويةبدون محدد وبدون IQueue (والذي سيكون مرهقًا)
  • طابور الأولويةمع محدد و IQueue

لقد تخلصت من المحدد تمامًا. إنه ضروري فقط إذا أردنا الحصول على PriorityQueue<T, U> واحد موحد. إذا كان لدينا فئتان - حسنًا ، يكفي المقارنة.


يرجى إعلامي إذا وجدت أي شيء يستحق الإضافة / التغيير / الإزالة.

تضمين التغريدة

يبدو المستند جيدًا. بدأت أشعر وكأنني حل قوي أتوقع أن أراه في مكتبات .NET الأساسية :)

  • يجب إضافة فئة العداد المتداخلة. لست متأكدًا مما إذا كان الوضع قد تغير ، ولكن منذ سنوات ، أدى وجود البنية الفرعية إلى تجنب تخصيص أداة تجميع القمامة الناتجة عن الملاكمة نتيجة GetEnumerator (). راجع https://github.com/dotnet/coreclr/issues/1579 على سبيل المثال.
    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(); } }

  • طابور الأولويةيجب أن يحتوي على بنية عدّد متداخلة أيضًا.

  • ما زلت أصوت للحصول على "public bool Remove (T element)" بدون TryRemove نظرًا لأنه نمط طويل الأمد وتغييره يجعل أخطاء المطورين محتملة للغاية. يمكننا السماح لطاقم API Review بالتناغم ، لكنه سؤال مفتوح في ذهني.

  • هل هناك أي قيمة في تحديد السعة الأولية في المُنشئ أو وجود وظيفة TrimExcess ، أم أن هذا التحسين الجزئي في الوقت الحالي - لا سيما بالنظر إلى IEnumerableمعلمات منشئ؟

pgolebiowski شكرًا لوضعه في مستند. هل يمكنك من فضلك تقديم مستندك بصفتك PR مقابل فرع CoreFxLab الرئيسي؟ علامة ianhayssafern سيكونون قادرين على دمج العلاقات العامة.
يمكننا بعد ذلك استخدام مشكلات CoreFxLab لإجراء مزيد من المناقشات حول نقاط تصميم معينة - يجب أن يكون الأمر أسهل من هذه المشكلة الضخمة (لقد بدأت للتو البريد الإلكتروني لإنشاء منطقة - PriorityQueue هناك).

يرجى وضع رابط لطلب سحب CoreFxLab هنا؟

  • ebickle @ jnm2 - مضاف
  • karelzSamuelEnglard - المشار إليها

إذا تمت الموافقة على واجهة برمجة التطبيقات في شكلها الحالي (فئتين) ، فسوف أقوم بإنشاء علاقات عامة أخرى إلى CoreFXLab مع تنفيذ لكليهما باستخدام كومة رباعية ( انظر التنفيذ ). سيستخدم PriorityQueue<T> مصفوفة واحدة تحتها فقط ، بينما يستخدم PriorityQueue<TElement, TPriority> مصفوفة اثنين - ويعيد ترتيب عناصرهما معًا. هذا يمكن أن يجنبنا مخصصات إضافية ويكون فعالاً قدر الإمكان. سأضيف أنه بمجرد أن يكون هناك ضوء أخضر.

هل هناك أي خطة لإنشاء إصدار آمن لمؤشر الترابط مثل ConcurrentQueue؟

أود: القلب: لمشاهدة نسخة متزامنة من هذا ، تنفذ IProducerConsumerCollection<T> ، لذا يمكن استخدامها مع BlockingCollection<T> إلخ.

khellangaobatact ويبدو وكأنه موضوع مختلف تماما:

أوافق على أنها قيمة للغاية على الرغم من: غمزة :!

public bool IsEmpty();

لماذا هذه طريقة؟ كل مجموعة أخرى في إطار العمل بها IsEmpty لها خاصية.

لقد قمت بتحديث الاقتراح في المنشور الأعلى بـ IsEmpty { get; } .
لقد أضفت أيضًا رابطًا لأحدث مستند مقترح في corefxlab repo.

تحية للجميع،
أعتقد أن هناك الكثير من الأصوات التي تجادل بأنه سيكون من الجيد دعم التحديث إن أمكن. أعتقد أنه لم ينته أحد من الشك في أنها ميزة مفيدة لعمليات البحث في الرسم البياني.

لكن كانت هناك بعض الاعتراضات التي أثيرت هنا وهناك. لذا يمكنني التحقق من فهمي ، يبدو أن الاعتراضات الرئيسية هي:
- ليس من الواضح كيف يجب أن يعمل التحديث عند وجود عناصر مكررة.
- هناك بعض الجدل حول ما إذا كان من المرغوب فيه دعم العناصر المكررة في قائمة انتظار الأولوية
- هناك بعض القلق من أنه قد يكون من غير الفعال أن يكون لديك بنية بيانات بحث إضافية للعثور على عناصر في بنية بيانات الدعم فقط لتحديث أولويتها. خاصة بالنسبة للسيناريوهات التي لا يتم فيها التحديث مطلقًا! و / أو تؤثر على ضمانات الأداء الأسوأ ...

أي شيء فاتني؟

حسنًا ، أعتقد أن هناك واحدة أخرى - لقد تم الادعاء بأن التحديث / الإزالة المعنوي بمعنى "updateFirst" أو "removeFirst" قد يكون غريبًا. :)

في أي إصدار من NET Core يمكننا البدء في استخدام PriorityQueue؟

memoryfraction .NET Core نفسه ليس لديه PriorityQueue حتى الآن (هناك مقترحات - لكن لم يتح لنا الوقت لقضائه عليها مؤخرًا). ومع ذلك ، لا يوجد سبب يجعله موجودًا في توزيع Microsoft .NET Core. يمكن لأي شخص في المجتمع وضع واحد على NuGet. ربما يمكن لشخص ما في هذه القضية أن يقترح واحدة.

memoryfraction .NET Core نفسه ليس لديه PriorityQueue حتى الآن (هناك مقترحات - لكن لم يتح لنا الوقت لقضائه عليها مؤخرًا). ومع ذلك ، لا يوجد سبب يجعله موجودًا في توزيع Microsoft .NET Core. يمكن لأي شخص في المجتمع وضع واحد على NuGet. ربما يمكن لشخص ما في هذه القضية أن يقترح واحدة.

شكرا لردك واقتراحك.
عندما أستخدم C # لممارسة leetcode ، وأحتاج إلى استخدام SortedSet للتعامل مع مجموعة مع عناصر مكررة. يجب أن أقوم بلف العنصر بمعرف فريد لحل المشكلة.
لذلك ، أفضل أن يكون لدي أولوية قائمة انتظار في .NET Core في المستقبل لأنه سيكون أكثر ملاءمة.

ما هو الوضع الحالي لهذه القضية؟ لقد وجدت نفسي مؤخرًا أحتاج إلى PriorityQueue

لا يقوم هذا الاقتراح بتمكين تهيئة المجموعة افتراضيًا. PriorityQueue<T> على طريقة Add . أعتقد أن تهيئة المجموعة هي ميزة لغوية كبيرة بما يكفي لضمان إضافة طريقة مكررة.

إذا كان لدى شخص ما كومة ثنائية سريعة يرغب في مشاركتها ، أود مقارنتها بما كتبته.
لقد نفذت واجهة برمجة التطبيقات بالكامل على النحو المقترح. ومع ذلك ، بدلاً من كومة ، فإنه يستخدم مصفوفة مرتبة بسيطة. احتفظ بالعنصر الأقل قيمة في النهاية بحيث يتم فرزها في عكس الترتيب المعتاد. عندما يكون عدد العناصر أقل من 32 ، أستخدم بحثًا خطيًا بسيطًا للعثور على مكان إدراج القيم الجديدة. بعد ذلك ، استخدمت بحثًا ثنائيًا. باستخدام البيانات العشوائية ، وجدت أن هذا الأسلوب أسرع من كومة ثنائية في الحالة البسيطة لملء قائمة الانتظار ثم استنزافها.
إذا كان لدي الوقت ، فسأضعه في git repo عام حتى يتمكن الأشخاص من نقده وتحديث هذا التعليق بالموقع.

SunnyWar أستخدمه حاليًا: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
التي لديها أداء جيد. أعتقد أنه سيكون من المنطقي اختبار التنفيذ الخاص بك مقابل GenericPriorityQueue هناك

الرجاء تجاهل منشوري السابق. لقد وجدت خطأ في الاختبار. بمجرد إصلاح الكومة الثنائية ، فإنها تحقق أفضل أداء.

karelz أين هذا الاقتراح حاليًا على ICollection<T> ؟ لم أفهم لماذا لن يتم دعم ذلك ، على الرغم من أنه من الواضح أنه ليس من المقصود أن تكون المجموعة للقراءة فقط؟

karelz إعادة السؤال المفتوح عن ترتيب dequeue: أنا أجادل لصالح أقل قيمة أولوية يتم إزالتها أولاً. إنه مناسب لخوارزميات أقصر مسار وهو أمر طبيعي لتطبيقات قائمة انتظار المؤقت أيضًا. وهذان هما السيناريوهان الرئيسيان اللذان أعتقد أنه سيتم استخدامه من أجلهما. لست على دراية بما ستكون عليه الحجة المعارضة ، ما لم تكن حجة لغوية تتعلق بأولويات "عالية" وقيم رقمية "عالية".

karelz re ترتيب التعداد ... ماذا عن وجود طريقة Sort() على النوع الذي سيضع كومة البيانات الداخلية للمجموعة في ترتيب مرتبة في المكان (في O (n log n)). واستدعاء Enumerate() بعد Sort() يعدد المجموعة في O (n). وإذا لم يتم استدعاء Sort () ، فسيتم إرجاع العناصر بترتيب غير مصنف في O (n)؟

الفرز: الانتقال إلى المستقبل حيث لا يبدو أن هناك إجماعًا حول ما إذا كان ينبغي علينا تنفيذ ذلك.

هذا محبط للغاية لسماع ذلك. لا يزال يتعين علي استخدام حلول الطرف الثالث لهذا الغرض.

ما هي الأسباب الأساسية لعدم رغبتك في استخدام حل طرف ثالث؟

بنية البيانات الكومة أمر لا بد منه لعمل leetcode
المزيد من leetcode ، والمزيد من المقابلات البرمجية c # مما يعني المزيد من مطوري c #.
المزيد من المطورين يعني نظام بيئي أفضل.
نظام بيئي أفضل يعني أنه إذا كان لا يزال بإمكاننا البرمجة في c # غدًا.

باختصار: هذه ليست ميزة فحسب ، بل هي أيضًا المستقبل. هذا هو السبب في أن القضية تسمى "المستقبل".

ما هي الأسباب الأساسية لعدم رغبتك في استخدام حل طرف ثالث؟

لأنه في حالة عدم وجود معيار ، يخترع كل شخص معاييره الخاصة ، ولكل منها مجموعة المراوغات الخاصة به.

كانت هناك مرات عديدة كنت أرغب فيها في الحصول على System.Collections.Generic.PriorityQueue<T> لأنه مرتبط بشيء أعمل عليه. هذا الاقتراح موجود منذ 5 سنوات حتى الآن. لماذا لم يحدث ذلك بعد؟

لماذا لم يحدث ذلك بعد؟

تجويع الأولوية ، لا يقصد التورية. يعد تصميم المجموعات أمرًا ناجحًا لأننا إذا وضعناها في BCL ، فعلينا التفكير في كيفية تبادلها ، وما هي الواجهات التي تنفذها ، وماهية الدلالات وما إلى ذلك. لا يعد أي من هذا علم الصواريخ ، ولكنه يستغرق بعض الوقت. علاوة على ذلك ، تحتاج إلى مجموعة من قضايا المستخدمين والزبائن الملموسة للحكم على التصميم وفقًا لها ، الأمر الذي يصبح أكثر صعوبة مع تزايد تخصص المجموعات. حتى الآن ، كان هناك دائمًا عمل آخر يعتبر أكثر أهمية من ذلك.

لنلقِ نظرة على مثال حديث: المجموعات غير القابلة للتغيير. تم تصميمها من قبل شخص لا يعمل في فريق BCL لحالة استخدام في VS كانت تدور حول الثبات. لقد عملنا معه للحصول على واجهات برمجة التطبيقات "BCL-ified". وعندما دخلت Roslyn على الإنترنت ، استبدلنا نسخها بنسخنا في أكبر عدد ممكن من الأماكن وقمنا بتعديل التصميم (والتنفيذ) كثيرًا بناءً على ملاحظاتهم. بدون سيناريو "بطل" هذا صعب.

لأنه في حالة عدم وجود معيار ، يخترع كل شخص معاييره الخاصة ، ولكل منها مجموعة المراوغات الخاصة به.

masonwheeler هل هذا ما شاهدته مقابل PriorityQueue<T> ؟ هل هناك العديد من خيارات الطرف الثالث غير القابلة للتبديل وليس "أفضل مكتبة لمعظم"؟ (لم أقم بالبحث لذا لا أعرف الجواب)

eiriktsarpalis كيف لا يوجد إجماع حول ما إذا كان سيتم التنفيذ؟

terrajobst هل تحتاج حقًا إلى سيناريو بطل عندما يكون هذا نمطًا مشهورًا مع تطبيقات معروفة؟ يجب أن يكون هناك القليل جدًا من الابتكار هنا. حتى أن هناك مواصفات واجهة مطورة بالكامل بالفعل. في رأيي ، السبب الحقيقي لعدم وجود هذه الأداة ليس أنها صعبة للغاية ، ولا يوجد طلب أو حالة استخدام لها. السبب الحقيقي هو أنه بالنسبة لأي مشروع برمجي حقيقي ، من الأسهل فقط بناء واحد بنفسك بدلاً من محاولة شن حملة سياسية لوضع هذا في مكتبات إطار العمل.

ما هي الأسباب الأساسية لعدم رغبتك في استخدام حل طرف ثالث؟

تضمين التغريدة
لقد قمت شخصيًا بتجربة أن حلول الطرف الثالث لا تعمل دائمًا كما هو متوقع / لا تستخدم ميزات اللغة الحالية. مع الإصدار القياسي أنا (كمستخدم) يمكن أن يكون على يقين تام من أن الأداء هو أفضل ما يمكنك الحصول عليه.

تضمين التغريدة

masonwheeler هل هذا ما شاهدته مقابل PriorityQueue<T> ؟ هل هناك العديد من خيارات الطرف الثالث غير القابلة للتبديل وليس "أفضل مكتبة لمعظم"؟ (لم أقم بالبحث لذا لا أعرف الجواب)

نعم فعلا. فقط google "C # priority queue" ؛ الصفحة الأولى مليئة بـ:

  1. عمليات تنفيذ قائمة الانتظار ذات الأولوية على Github ومواقع الاستضافة الأخرى
  2. الناس يسألون لماذا في العالم لا توجد قائمة انتظار رسمية للأولوية في المجموعات
  3. برامج تعليمية حول كيفية إنشاء تنفيذ قائمة انتظار الأولوية الخاصة بك

terrajobst هل تحتاج حقًا إلى سيناريو بطل عندما يكون هذا نمطًا مشهورًا مع تطبيقات معروفة؟ يجب أن يكون هناك القليل جدًا من الابتكار هنا.

في تجربتي نعم. الشيطان يكمن في التفاصيل وبمجرد أن نشحن API ، لا يمكننا فعلاً إجراء تغييرات جذرية. وهناك العديد من خيارات التنفيذ التي يمكن للمستخدم رؤيتها. يمكننا معاينة واجهة برمجة التطبيقات باعتبارها OOB لفترة من الوقت ، لكننا تعلمنا أيضًا أنه بينما يمكننا بالتأكيد جمع التعليقات ، فإن عدم وجود سيناريو بطل يعني أنه ليس لديك أي قواطع ربطة عنق ، مما يؤدي غالبًا إلى موقف يكون فيه البطل عند وصول السيناريو ، فإن بنية البيانات لا تفي بمتطلباتها.

على ما يبدو نحن بحاجة إلى بطل. 😛

masonwheeler افترضت أن رابطك سيكون لهذا 😄 الآن هو في رأسي.

على الرغم من أنه كما يقول terrajobst ، فإن

[تم تعديله من أجل الوضوح]

danmosemsft Nah ، إذا كنت فسأفعل إصدار Shrek 2.

مرشح تطبيق البطل رقم 1: ماذا عن استخدام واحد في TimerQueue.Portable؟

https://github.com/dotnet/runtime/blob/4f9ae42d861fcb4be2fcd5d3d55d5f227d30e723/src/libraries/System.Private.CoreLib/src/System/Threading/TimerQueue.Portable.cs

مرشح تطبيق البطل رقم 1: ماذا عن استخدام واحد في TimerQueue.Portable؟

تم بالفعل النظر فيه ، ونمذجه ، والتخلص منه. إنه يجعل الحالة الشائعة جدًا وهي إنشاء مؤقت وتدمير سريع (على سبيل المثال لانتهاء المهلة) أقل كفاءة.

stephentoub أفترض أنك تقصد أنها أقل كفاءة في بعض السيناريوهات حيث يوجد عدد قليل من أجهزة ضبط الوقت. لكن ما هو مقياسها؟

أفترض أنك تقصد أنها أقل كفاءة في بعض السيناريوهات حيث يوجد عدد قليل من أجهزة ضبط الوقت. لكن ما هو مقياسها؟

لا ، قصدت أن الحالة الشائعة هي أن لديك الكثير من أجهزة ضبط الوقت في أي لحظة ، لكن القليل جدًا منهم يطلقون النار. هذا هو ما ينتج عند استخدام الموقتات للمهلة. والشيء المهم هو السرعة التي يمكنك بها الإضافة والإزالة من بنية البيانات ... تريد أن تكون 0 (1) وبنسبة حمل منخفضة جدًا. إذا أصبح ذلك O (سجل N) ، فهذه مشكلة.

إن وجود قائمة انتظار ذات أولوية سيجعل بالتأكيد C # أكثر ملاءمة للمقابلة.

إن وجود قائمة انتظار ذات أولوية سيجعل بالتأكيد C # أكثر ملاءمة للمقابلة.

نعم هذا صحيح.
أنا أبحث عنه لنفس السبب.

stephentoub بالنسبة إلى المهلات التي لا تحدث في الواقع ، هذا منطقي تمامًا بالنسبة لي. لكني أتساءل ما الذي يحدث للنظام عندما يبدأ فجأة في الحدوث الكثير من المهلات ، لأنه فجأة يكون هناك فقدان للحزم أو خادم لا يستجيب أو أي شيء آخر؟ هل تستخدم أجهزة ضبط الوقت المتكررة أيضًا System.Timer نفس التنفيذ؟ هناك ، سيكون انتهاء المهلة هو "الطريق السعيد".

لكني أتساءل ماذا يحدث للنظام عندما يبدأ فجأة في الحدوث الكثير من المهلات

جربها. :)

لقد رأينا قدرًا كبيرًا من أعباء العمل الحقيقية تعاني من التنفيذ السابق. في كل حالة نظرنا إليها ، تم حل المشكلة الجديدة (التي لا تستخدم قائمة انتظار ذات أولوية ولكن بدلاً من ذلك تحتوي على تقسيم بسيط بين أجهزة ضبط الوقت قريبًا وغير قريب) ، ولم نر مشاكل جديدة بها هو - هي.

هل تستخدم أجهزة ضبط الوقت المتكررة أيضًا System.Timer نفس التنفيذ؟

نعم فعلا. لكنها منتشرة بشكل عام ، غالبًا بشكل متساوٍ إلى حد ما ، في تطبيقات العالم الحقيقي.

بعد 5 سنوات ، لا يوجد حتى الآن PriorityQueue.

تتمتع Rx بفئة انتظار ذات أولوية عالية تم اختبارها على مستوى الإنتاج:

https://github.com/Reactive-Extensions/Rx.NET/blob/master/Rx.NET/Source/System.Reactive.Core/Reactive/Internal/PriorityQueue.cs

image

بعد 5 سنوات ، لا يوجد حتى الآن PriorityQueue.

يجب ألا تكون أولوية عالية بما يكفي ...

لقد تغيروا حول تخطيط الريبو. الموقع الجديد هو https://github.com/dotnet/reactive/blob/master/Rx.NET/Source/src/System.Reactive/Internal/PriorityQueue.cs

stephentoub ولكن ربما في الواقع eiriktsarpalis ، هل لدينا أي تأثير على هذا الآن؟ إذا كان لدينا تصميم نهائي لواجهة برمجة التطبيقات ، فسأكون على استعداد للعمل عليه

لم أر إعلانًا تمت تسويته بعد ولست متأكدًا مما إذا كان يمكن أن يكون هناك تصميم نهائي لواجهة برمجة التطبيقات بدون تطبيق قاتل محدد. لكن...
بافتراض أن أفضل مرشح للتطبيق القاتل هو برمجة المسابقات / التدريس / المقابلات ، أعتقد أن تصميم إريك في الأعلى يبدو صالحًا للخدمة بدرجة كافية ... ولا يزال لدي اقتراحي المضاد جالسًا (تمت مراجعته حديثًا ، ولم يتم سحبه بعد!)

https://github.com/TimLovellSmith/corefxlab/blob/priorityQueueSpecSolved/docs/specs/priority-queue.md

الاختلافات الرئيسية التي ألاحظها بينهما هي ما إذا كان ينبغي محاولة التحدث مثل القاموس ، وما إذا كان يجب أن تكون المحددات شيئًا.

بدأت أنسى بالضبط سبب رغبتي في أن يكون قاموسًا. إن وجود مُخصص مُفهرس لتحديث الأولويات ، والقدرة على تعداد المفاتيح حسب أولوياتها ، بدا لي أمرًا طيبًا وشبيهًا جدًا بالقاموس. قد تكون القدرة على تصورها كقاموس في مصحح الأخطاء أمرًا رائعًا أيضًا.

أعتقد أن المحددات في الواقع تغير نوعًا ما بشكل جذري من واجهة الفئة المتوقعة ، FWIW. يجب أن يكون المحدِّد شيئًا مخبوزًا في وقت البناء ، وإذا كان لديك ، فإنه يزيل الحاجة إلى أن يمرر أي شخص قيمة أولوية لأي شخص آخر - يجب عليهم فقط الاتصال بالمحدد إذا كانوا يريدون معرفة الأولوية. لذلك لن ترغب في الحصول على معلمات الأولوية في أي توقيعات طريقة على الإطلاق باستثناء المحدد إلى حد كبير. لذلك في هذه الحالة يصبح نوعًا من فئة "PrioritySet" الأكثر تخصصًا. [لأي محددات غير نقية هي مشكلة خطأ محتملة!]

@ TimLovellSmith أتفهم الرغبة في هذا السلوك ، ولكن نظرًا لأن هذا كان طلبًا لمدة 5 سنوات ، ألن يكون من المعقول تنفيذ كومة ثنائية قائمة على المصفوفة باستخدام محدد ومشاركة نفس مساحة سطح واجهة برمجة التطبيقات مثل Queue.cs ؟ أعتقد أن هذا هو ما يحتاجه معظم المستخدمين. في رأيي ، لم يتم تنفيذ هيكل البيانات هذا لأننا لا نستطيع الموافقة على تصميم واجهة برمجة التطبيقات ، ولكن إذا كنا نصنع PriorityQueue أعتقد أنه يجب علينا فقط محاكاة تصميم واجهة برمجة التطبيقات Queue .

Jlalond ومن هنا الاقتراح هنا ، أليس كذلك؟ ليس لدي أي اعتراض على تنفيذ مجموعة تستند إلى كومة. أتذكر الآن أكبر اعتراض لدي مقابل المحددات ، هو أنه ليس من السهل إجراء التحديثات ذات الأولوية بشكل صحيح . لا تحتوي قائمة الانتظار القديمة البسيطة على عملية تحديث ذات أولوية ، ولكن تحديث أولوية العنصر يعد عمليات مهمة للعديد من خوارزميات قائمة الانتظار ذات الأولوية.
يجب أن يكون الناس شاكرين إلى الأبد إذا استخدمنا واجهة برمجة تطبيقات لا تتطلب منهم تصحيح أخطاء هياكل قائمة انتظار الأولوية التالفة.

@ TimLovellSmith آسف تيم ، كان علي توضيح الميراث من IDictionary .

هل لدينا حالة استخدام حيث ستتغير الأولوية؟

يعجبني تطبيقك ، لكنني أعتقد أنه يمكننا تقليل تكرار سلوك IDictionary. أعتقد أنه لا يجب أن نرث من IDictionary<> ولكن فقط ICollection ، لأنني لا أعتقد أن أنماط الوصول إلى القاموس بديهية ،

ومع ذلك ، أعتقد حقًا أن إعادة T والأولوية المرتبطة بها ستكون منطقية ، لكنني لا أعرف حالة استخدام حيث سأحتاج إلى معرفة أولوية عنصر داخل بنية البيانات عند إدراجها في قائمة أو إلغاء ترتيبها.

تضمين التغريدة
إذا كانت لدينا قائمة انتظار ذات أولوية ، فيجب أن تدعم جميع العمليات التي يمكنها ببساطة وفعالية _و_
من المتوقع أن يكون هناك أشخاص على دراية بهذا النوع من بنية البيانات / التجريد.

تحديث الأولوية ينتمي إلى API لأنه يقع في كلتا الفئتين. يعد تحديث الأولوية أمرًا مهمًا بدرجة كافية في الاعتبار في العديد من الخوارزميات بحيث يؤثر تعقيد إجراء العملية باستخدام هياكل بيانات الكومة على تعقيد الخوارزمية الكلية ، ويتم قياسها بانتظام ، انظر:
https://en.wikipedia.org/wiki/Priority_queue#Specialized_heaps

TBH بشكل أساسي مفتاح تقليله المثير للاهتمام بالنسبة للخوارزميات والكفاءة ، ويتم قياسه ، لذا فأنا شخصيًا على ما يرام بالنسبة لواجهة برمجة التطبيقات (API) التي لا تحتوي إلا على DecreasePriority () ، وليس لديها UpdatePriority بالفعل.
ولكن للراحة ، يبدو من الأجمل عدم إزعاج مستخدمي واجهة برمجة التطبيقات (API) بالقلق بشأن ما إذا كان كل تغيير هو زيادة أم نقصان. حتى يكون لدى مطور joe "تحديث" خارج الصندوق دون الحاجة إلى التساؤل عن سبب دعمه للتقليل فقط وليس الزيادة ، (وأيها يستخدم متى) ، أعتقد أنه من الأفضل أن يكون لديك واجهة برمجة تطبيقات ذات أولوية تحديث عامة ، ولكن قم بتوثيق ذلك عند استخدامه لتقليل التكلفة س ، عند استخدامه للزيادة تكلفته Y لأنه يتم تنفيذه مثل إزالة + إضافة.

لقد وافقت على قاموس RE. إزالته من اقتراحي باسم أبسط أفضل.
https://github.com/TimLovellSmith/corefxlab/blob/priorityQueueSpecSolved/docs/specs/priority-queue.md

لا أعرف حالة استخدام حيث سأحتاج إلى معرفة أولوية عنصر داخل بنية البيانات عند إدراجها في قائمة الانتظار أو إلغاء ترتيبها.

لست متأكدًا مما يدور هذا التعليق. لا تحتاج إلى معرفة أولوية عنصر ما لإلغاء ترتيبها. لكنك تحتاج إلى معرفة أولويتها عند إدراجها في قائمة. ربما ترغب في معرفة الأولوية النهائية لعنصر ما عندما تقوم بإلغاء تحديده ، لأن هذه القيمة يمكن أن يكون لها معنى مثل المسافة.

تضمين التغريدة

لست متأكدًا مما يدور هذا التعليق. لا تحتاج إلى معرفة أولوية عنصر ما لإلغاء ترتيبها. لكنك تحتاج إلى معرفة أولويتها عند إدراجها في قائمة. ربما ترغب في معرفة الأولوية النهائية لعنصر ما عندما تقوم بإلغاء تحديده ، لأن هذه القيمة يمكن أن يكون لها معنى مثل المسافة.

عذرًا ، لقد أسأت فهم ما تعنيه TPriorty في زوج القيمة الرئيسية الخاص بك.

حسنًا ، السؤالان الأخيران ، لماذا هما KeyValuePair مع TPriority ، وأفضل وقت يمكنني رؤيته لإعادة ترتيب الأولويات هو N log N ، أوافق على أنه ذو قيمة ، ولكن أيضًا كيف ستبدو واجهة برمجة التطبيقات تلك؟

أفضل وقت يمكنني رؤيته لإعادة تحديد الأولويات هو N log N ،

هذا هو أفضل وقت لإعادة ترتيب أولويات العناصر N بدلاً من عنصر واحد ، أليس كذلك؟
سأوضح المستند: "UpdatePriority | O (log n)" يجب أن تقرأ "UpdatePriority (عنصر واحد) | O (log n)".

TimLovellSmith أمر منطقي ، لكن أليس من الممكن أن يكون هناك أي تحديث للأولوية في الواقع ne O2 * (log n) ، لأننا سنحتاج إلى إزالة العنصر ثم إعادة إدراجه؟ بناء على افتراضاتي أن هذا هو كومة ثنائية

TimLovellSmith أمر منطقي ، لكن أليس من الممكن أن يكون هناك أي تحديث للأولوية في الواقع ne O2 * (log n) ، لأننا سنحتاج إلى إزالة العنصر ثم إعادة إدراجه؟ بناء على افتراضاتي أن هذا هو كومة ثنائية

يتم تجاهل العوامل الثابتة مثل 2 بشكل عام في تحليل التعقيد لأنها تصبح في الغالب غير ذات صلة مع نمو حجم N.

فهمت ، أرادت في الغالب معرفة ما إذا كان لدى تيم أي أفكار مثيرة ليفعلها فقط
عملية واحدة :)

في الثلاثاء ، 18 أغسطس ، 2020 ، 23:51 كتب masonwheeler [email protected] :

TimLovellSmith https://github.com/TimLovellSmith المنطقي، ولكن
لن يكون أي تحديث للأولوية في الواقع ne O2 * (log n) ، كما نحتاج إلى ذلك
إزالة العنصر ثم إعادة إدراجه؟ بناء على افتراضاتي على هذا
كونها كومة ثنائية

يتم تجاهل العوامل الثابتة مثل 2 بشكل عام في تحليل التعقيد
لأنها تصبح في الغالب غير ذات صلة مع نمو حجم N.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/dotnet/runtime/issues/14032#issuecomment-675887763 ،
أو إلغاء الاشتراك
https://github.com/notifications/unsubscribe-auth/AF76XTI3YK4LRUVTOIQMVHLSBNZARANCNFSM4LTSQI6Q
.

تضمين التغريدة
لا توجد أفكار جديدة ، كنت أتجاهل عاملاً ثابتًا ، لكن الانخفاضات أيضًا تختلف عن الزيادات

إذا كان التحديث ذو الأولوية يمثل نقصًا ، فلن تضطر إلى إزالته وإعادة إضافته ، فهو مجرد فقاعات ، لذا فإن 1 * O (تسجيل ن) = O (تسجيل ن).
إذا كان التحديث ذو الأولوية هو زيادة ، فربما يتعين عليك إزالته وإعادة إضافته ، لذلك 2 * O (تسجيل الدخول) = لا يزال O (تسجيل ن).

ربما يكون شخص آخر قد اخترع بنية بيانات / خوارزمية أفضل لزيادة الأولوية من إزالة + قراءة ، لكنني لم أحاول العثور عليها ، يبدو أن O (سجل ن) جيد بما فيه الكفاية.

تسلل KeyValuePair إلى الواجهة بينما كان قاموسًا ، لكنه نجا من إزالة القاموس ، وذلك بشكل أساسي بحيث يمكن تكرار مجموعة العناصر مع أولوياتها. وأيضًا حتى تتمكن من فصل العناصر عن أولوياتها. ولكن ربما من أجل التبسيط مرة أخرى ، يجب أن يكون إلغاء ترتيب الأولويات فقط في الإصدار "المتقدم" من Dequeue API. (TryDequeue: D)

سأقوم بهذا التغيير.

@ TimLovellSmith Cool ، أتطلع إلى

تضمين التغريدة
لقد أجريت التغييرات الطفيفة لتبسيط واجهة Peek + Dequeue.
بعض ICollectionapis المتعلقة بـ KeyValuePair لا تزال قائمة ، لأنني لا أرى أي شيء من الواضح أنه من الأفضل استبدالها.
نفس رابط العلاقات العامة. هل هناك طريقة أخرى لتقديمها للمراجعة؟

https://github.com/TimLovellSmith/corefxlab/blob/priorityQueueSpecSolved/docs/specs/priority-queue.md

لا مانع من التطبيق الذي يستخدمه طالما أنه سريع مثل كومة قائمة على المصفوفة أو أفضل.

@ TimLovellSmith لا أعرف شيئًا عن مراجعة واجهة برمجة التطبيقات ، لكني أتخيل أن @ danmosemsft يمكن أن

سؤالي الأول هو ماذايكون؟ أفترض أنه سيكون رقمًا صحيحًا أو رقمًا فاصلة عائمة ، ولكن بالنسبة لي ، فإن إعلان الرقم "الداخلي" لقائمة الانتظار يبدو غريبًا بالنسبة لي

وأنا في الواقع أفضل الأكوام الثنائية ، لكني أردت التأكد من أننا جميعًا على ما يرام في هذا الاتجاه

layomiaeiriktsarpalis هي أصحاب المنطقة ويجب أن يرعى ذلك من خلال مراجعة API. لم أتابع المناقشة ، هل وصلنا إلى نقطة توافق؟

كما ناقشنا في الماضي ، فإن المكتبات الأساسية ليست أفضل مكان لجميع المجموعات ، خاصة تلك التي لها رأي و / أو متخصصة. على سبيل المثال ، نحن نشحن سنويًا - لا يمكننا التحرك بأسرع ما يمكن للمكتبة. لدينا هدف لبناء مجتمع حول حزمة مجموعات لملء هذه الفجوة. لكن 84 إبهامًا على هذا يشير إلى وجود حاجة واسعة النطاق لهذا في المكتبات الأساسية.

Jlalond آسف ، لم أفهم تمامًا ما يفترض أن يكون السؤال الأول هناك. هل تسأل لماذا الأولوية عامة؟ أو يجب أن يكون رقمًا؟ أشك في أن العديد من الأشخاص سيستخدمون أنواع الأولوية غير الرقمية. لكنهم قد يفضلون استخدام بايت أو int أو long أو float أو double أو enum.

@ TimLovellSmith Yep ، أردت فقط معرفة ما إذا كانت عامة

تضمين التغريدة عدد الاعتراضات / التعليقات التي لم تتم معالجتها والتي أراها على اقتراحي [1] هي صفر تقريبًا ، وهي تتناول أي أسئلة مهمة تركها اقتراح ebickle في الأعلى (والذي أصبح مشابهًا له بشكل متزايد).

لذلك أدعي أنها اجتازت فحوصات السلامة حتى الآن. يجب أن يكون هناك بعض المراجعة المطلوبة حتى الآن ، يمكننا التحدث عما إذا كان من المفيد وراثة مجموعة IReadOnlyCollection (تبدو غير مفيدة جدًا ولكن يجب أن أرجع للخبراء) وما إلى ذلك - أعتقد أن هذا هو الغرض من عملية مراجعة API! eiriktsarpalislayomia هل لي أن أطلب منك إلقاء نظرة؟

[1] https://github.com/TimLovellSmith/corefxlab/blob/priorityQueueSpecSolved/docs/specs/priority-queue.md

[ملاحظة - استنادًا إلى إحساس الخيط ، فإن التطبيق القاتل المقترح حاليًا هو مسابقات الترميز ، وأسئلة المقابلة ، وما إلى ذلك ، حيث تريد فقط واجهة برمجة تطبيقات بسيطة لطيفة لاستخدامها إما في الفئات أو الهياكل بالإضافة إلى النوع الرقمي المفضل لديك في اليوم ، باستخدام حرف O الصحيح (ن) وليس التنفيذ بطيئًا جدًا - ويسعدني أن أكون خنزير غينيا لتطبيقه على بعض المشكلات. آسف ، ليس هناك أي شيء أكثر إثارة.]

مجرد التحدث لأنني أستخدم قوائم الانتظار ذات الأولوية طوال الوقت للمشاريع "الحقيقية" ، في المقام الأول للبحث في الرسم البياني (مثل تحسين المشكلة ، وإيجاد المسار) ، والاستخراج المرتب (عن طريق القطع (على سبيل المثال ، أقرب شجرة رباعية الجيران) ، والجدولة (على سبيل المثال ، مثل مناقشة المؤقت أعلاه)). لديّ مشروعان يمكنني توصيل تطبيق بهما مباشرةً لفحص / اختبار سلامة العقل ، إذا كان ذلك مفيدًا. يبدو الاقتراح الحالي جيدًا بالنسبة لي (سيعمل مع جميع أغراضي ، إن لم يكن مناسبًا بشكل مثالي). لدي بعض الملاحظات الصغيرة:

  • يظهر TElement بضع مرات حيث يجب أن يكون TKey
  • غالبًا ما تتضمن تطبيقاتي الخاصة bool EnqueueOrUpdateIfHigherPriority لسهولة الاستخدام (وفي بعض الحالات الكفاءة) ، والتي غالبًا ما ينتهي بها الأمر إلى كونها الطريقة الوحيدة للإدراج / التحديث التي يتم استخدامها (على سبيل المثال في البحث في الرسم البياني). من الواضح أنه غير ضروري وسيضيف تعقيدًا إضافيًا إلى واجهة برمجة التطبيقات ، ولكن من الجيد جدًا امتلاكه.
  • هناك نسختان من Enqueue (لا أعني Add ): هل من المفترض أن تكون النسخة bool TryEnqueue ؟
  • "// تعداد المجموعة بترتيب عشوائي ، ولكن مع العنصر الأصغر أولاً": لا أعتقد أن الجزء الأخير مفيد ؛ أفضل ألا يضطر العداد إلى إجراء أي مقارنات ، لكنني أفترض أن هذا سيكون "مجانيًا" لذلك لن يزعجني
  • تسمية EqualityComparer غير متوقعة بعض الشيء: كنت أتوقع أن يذهب KeyComparer مع PriorityComparer .
  • لا أفهم الملاحظات المتعلقة بتعقيد CopyTo و ToArray .

تضمين التغريدة
شكرا جزيلا لاستعراض لك!

يعجبني اقتراح التسمية الخاص بـ KeyComparer. يبدو أن واجهة برمجة التطبيقات ذات الأولوية المنخفضة مفيدة للغاية. ماذا عن إضافة واحدة من تلك واجهات برمجة التطبيقات أو كليهما. نظرًا لأننا نستخدم نموذج "الأولوية الأقل تأتي أولاً" ، فهل ستستخدم تقليل فقط؟ أو هل ترغب في الزيادة أيضًا؟

    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

السبب في أنني اعتقدت أن تعداد إرجاع العنصر الأقل أولاً هو أن يكون متسقًا مع النموذج العقلي الذي يقوم Peek () بإرجاع العنصر في مقدمة المجموعة. وأيضًا لا توجد مقارنات ضرورية لتنفيذه إذا كانت عبارة عن كومة ثنائية ، لذلك بدت وكأنها هدية مجانية. لكن ربما يكون أقل حرية مما أعتقد - تعقيد غير مبرر؟ أنا سعيد لإخراجها لهذا السبب.

اثنين من الطوابع كان عرضي. لدينا EnqueueOrUpdate ، حيث يتم تحديث الأولوية دائمًا. أعتقد أن TryUpdate هو معكوس منطقي لذلك ، حيث لا ينبغي أبدًا تحديث الأولوية للعناصر الموجودة بالفعل في المجموعة. أستطيع أن أرى أنه يملأ تلك الفجوة المنطقية ولكني لست متأكدًا مما إذا كانت واجهة برمجة تطبيقات سيرغب الناس في الممارسة العملية.

يمكنني فقط أن أتخيل استخدام المتغير المتناقص: إنها عملية طبيعية في العثور على المسار الرغبة في إدراج عقدة أو استبدال عقدة موجودة بأولوية أقل ؛ لم أستطع اقتراح استخدام للعكس على الفور. يمكن أن تكون معلمة الإرجاع boolean سهلة الاستخدام إذا احتجت إلى تنفيذ أي عملية عند وضع العنصر في قائمة الانتظار / عدم وضعه في قائمة الانتظار.

لم أكن أفترض وجود كومة ثنائية ، ولكن إذا كانت مجانية ، فليس لدي مشكلة في كون العنصر الأول دائمًا هو الحد الأدنى ، فقد بدا مجرد قيد غريب.

VisualMelonTimLovellSmith ليت API واحد يكون على ما يرام؟
EnqueueOrUpdatePriority ؟ أتخيل أن مجرد القدرة على تغيير موضع عقدة داخل قائمة الانتظار تعتبر ذات قيمة لخوارزميات الاجتياز حتى لو كانت تزيد أو تقلل من الأولوية

Jlalond نعم ، أذكره لأنه يمكن أن يكون مفيدًا للكفاءة اعتمادًا على التنفيذ ، ويشفر مباشرةً حالة الاستخدام المتمثلة في الرغبة في وضع شيء في قائمة الانتظار فقط إذا كان يحسن ما لديك بالفعل في قائمة الانتظار. لن أفترض أن أقول ما إذا كان التعقيد الإضافي مناسبًا لمثل هذه الأداة ذات الأغراض العامة ، لكنه بالتأكيد ليس ضروريًا.

تم التحديث لإزالة EnqueueOrIncreasePriority ، مع الاحتفاظ EnqueueOrUpdate و EnqueueOrDecrease. السماح لهم بإرجاع منطقي عند إضافة عنصر حديثًا إلى المجموعة ، مثل HashSet.Add ().

Jlalond أعتذر عن الخطأ أعلاه (حذف آخر تعليق لك يسألك عن موعد مراجعة هذه المشكلة).

أردت فقط أن أذكر أن eiriktsarpalis سيعود الأسبوع المقبل وسنناقش تحديد أولويات هذه الميزة ومراجعة واجهات برمجة التطبيقات بعد ذلك.

هذا شيء نفكر فيه ولكننا لم نلتزم به بعد لـ .NET 6.

أرى أنه تم إحياء هذا الموضوع من خلال البدء من نقطة الصفر والتقسيم ، على الرغم من أننا أجرينا مناقشات متعمقة حول هذا الموضوع في عام 2017 لعدة أشهر (غالبية هذا الموضوع الضخم) وأنتجنا هذا الاقتراح بشكل جماعي - قم بالتمرير أعلاه لترى. هذا أيضًا هو الاقتراح الذي ربطتهkarelz في السطر الأول من

راجع أحدث اقتراح في corefxlab repo.

اعتبارًا من 2017-06-16 ، كنا ننتظر مراجعة API التي لم تحدث مطلقًا وكانت الحالة:

إذا تمت الموافقة على واجهة برمجة التطبيقات في شكلها الحالي (فئتين) ، فسوف أقوم بإنشاء علاقات عامة أخرى إلى CoreFXLab مع تنفيذ لكليهما باستخدام كومة رباعية ( انظر التنفيذ ). سيستخدم PriorityQueue<T> مصفوفة واحدة تحتها فقط ، بينما يستخدم PriorityQueue<TElement, TPriority> مصفوفة اثنين - ويعيد ترتيب عناصرهما معًا. هذا يمكن أن يجنبنا مخصصات إضافية ويكون فعالاً قدر الإمكان. سأضيف أنه بمجرد أن يكون هناك ضوء أخضر.

أوصي بمواصلة تكرار الاقتراح الذي قدمناه في عام 2017 ، بدءًا من المراجعة الرسمية التي لم تحدث بعد. سيسمح لنا ذلك بتجنب التشريف والتكرار على خلفية الاقتراح الذي تم تجميعه معًا بعد تبادل مئات المنشورات من قبل أعضاء المجتمع ، وأيضًا احترامًا للجهود التي يبذلها جميع المعنيين في هذا الأمر. يسعدني مناقشته إذا كانت هناك تعليقات.

pgolebiowski شكرًا على عودتك إلى المناقشة. أود أن أعتذر عن تقديم الاقتراحات بشكل فعال ، وهي ليست الطريقة الأكثر فعالية للتعاون. لقد كانت خطوة اندفاعية من جانبي. (كان لدي انطباع بأن المناقشة قد توقفت تمامًا بسبب وجود عدد كبير جدًا من الأسئلة المفتوحة في المقترحات بعيدًا جدًا ، وكنت بحاجة فقط إلى اقتراح أكثر إبداءً للرأي.)

ماذا لو حاولنا المضي قدمًا في هذا الأمر ، عن طريق نسيان محتوى رمز العلاقات العامة الخاصة بي مؤقتًا على الأقل ، واستئناف مناقشة هنا "المشكلات المفتوحة" المحددة؟ أود إبداء رأيي في كل منهم.

  1. اسم الفئة PriorityQueue vs. Heap <- أعتقد أنه تمت مناقشته بالفعل والإجماع هو أن PriorityQueue أفضل.

  2. تقديم IHeap والحمل الزائد للمنشئ؟ <- لا أتوقع الكثير من القيمة. أعتقد أنه بالنسبة لـ 95٪ من العالم لا يوجد سبب مقنع (على سبيل المثال دلتا الأداء) لوجود العديد من تطبيقات الكومة ، ومن المحتمل أن يكتب 5٪ الآخرون بأنفسهم.

  3. إدخال IPriorityQueue؟ <- أنا أيضًا لا أعتقد أنه مطلوب. أفترض أن اللغات والأطر الأخرى تتوافق تمامًا بدون هذه الواجهات في مكتبتها القياسية. اسمحوا لي أن أعرف إذا كان هذا غير صحيح.

  4. استخدم المحدد (للأولوية المخزنة داخل القيمة) أو لا (اختلاف 5 واجهات برمجة التطبيقات) <- لا أرى حجة قوية لصالح دعم المحددات في قائمة انتظار الأولوية. أشعر أن IComparer<> يعمل بالفعل كآلية توسع قليلة جيدة حقًا لمقارنة أولويات العناصر ، وأن المحددات لا تقدم أي تحسينات كبيرة. من المحتمل أيضًا أن يتم الخلط بين الأشخاص حول كيفية استخدام المحددات ذات الأولويات القابلة للتغيير (أو كيفية عدم استخدامها).

  5. استخدم tuples (عنصر TElement ، أولوية أولوية TP) مقابل KeyValuePair<- شخصيًا أعتقد أن KeyValuePair هو الخيار المفضل لقائمة انتظار الأولوية حيث يكون للعناصر أولويات قابلة للتحديث ، حيث يتم التعامل مع العناصر كمفاتيح في مجموعة / قاموس.

  6. هل يجب أن يكون لدى كل من Peek و Dequeue حجة بدلاً من tuple؟ <- لست متأكدًا من كل الإيجابيات والسلبيات ، خاصة الأداء. هل يجب أن نختار الخيار الأفضل أداءً في اختبار معياري بسيط؟

  7. هل رمي Peek و Dequeue مفيد على الإطلاق؟ <- نعم ... هذا ما يجب أن يحدث للخوارزميات التي تفترض خطأً أنه لا تزال هناك عناصر في قائمة الانتظار . إذا كنت لا تريد أن تفترض الخوارزميات ذلك على الإطلاق ، فإن أفضل ما يمكنك فعله هو عدم توفير واجهات برمجة تطبيقات Peek و Dequeue على الإطلاق ، ولكن فقط قم بتوفير TryPeek و TryDequeue. [أعتقد أن هناك خوارزميات يمكن إثباتها بأمان استدعاء Peek أو Dequeue في حالات معينة ، ووجود Peek / Dequeue هو تحسين طفيف في الأداء + قابلية الاستخدام لهؤلاء.]

بصرف النظر عن ذلك ، لدي أيضًا بعض الاقتراحات للنظر في إضافة إلى الاقتراح قيد النظر:

  1. طابور الأولويةيجب أن تعمل فقط مع العناصر الفريدة ، والتي هي مقبضها الخاص. يجب أن يدعم IEqualityComparer المخصص ، في حالة رغبته في مقارنة الكائنات غير القابلة للتوسيع (مثل السلاسل) بطرق محددة لإجراء التحديثات ذات الأولوية.

  2. طابور الأولويةيجب أن تدعم مجموعة متنوعة من العمليات مثل "إزالة" ، و "تقليل الأولوية إذا كانت أصغر" ، وخاصة "إدراج عنصر في قائمة الانتظار أو تقليل الأولوية إذا كانت العملية أصغر" ، كل ذلك في O (سجل n) - من أجل تنفيذ سيناريوهات بحث الرسم البياني بكفاءة وبساطة.

  3. سيكون من المناسب للمبرمجين الكسالى إذا كان PriorityQueueيوفر عامل تشغيل الفهرس [] للحصول على / تعيين أولوية العناصر الموجودة. يجب أن يكون O (1) للحصول على ، O (تسجيل الدخول) للمجموعة.

  4. الأولوية الأصغر أولاً هي الأفضل. نظرًا لاستخدام قوائم الانتظار ذات الأولوية في كثير من الأحيان ، فهي تمثل مشكلات في التحسين ، بأقل تكلفة ممكنة.

  5. يجب ألا يوفر التعداد أي ضمانات للأمر.

إصدار مفتوح - مقابض:
طابور الأولويةيبدو أن الغرض من العناصر والأولويات هو السماح بالعمل باستخدام قيم مكررة. يجب اعتبار هذه القيم إما "غير قابلة للتغيير" ، أو يجب أن تكون هناك طريقة للاتصال بإخطار المجموعة عند تغيير أولويات العنصر ، ربما باستخدام المقابض ، بحيث يمكن أن تكون محددة حول أولوية التكرارات التي تم تغييرها (ما السيناريو الذي يجب القيام به هذا ؟؟) ... لدي شكوك حول كل هذا مفيد ، وما إذا كانت هذه فكرة جيدة ، ولكن إذا كان لدينا تحديث لأولويات في مثل هذه المجموعة ، فقد يكون من الجيد أن يتم تكبد التكاليف التي تكبدتها هذه الطريقة بشكل كسول ، لذلك أنه عندما تعمل فقط على عناصر غير قابلة للتغيير ، ولا ترغب في استخدامها ، فلا توجد تكلفة إضافية ... كيف تحل؟ هل يمكن أن يكون ذلك من خلال الطرق المثقلة التي تستخدم المقابض وتلك التي لا تفعل ذلك؟ أو لا ينبغي أن يكون لدينا أي مقابض عناصر مختلفة عن العناصر نفسها (تُستخدم كمفتاح تجزئة للبحث)؟

فكرة للمساعدة في نقل هذا بشكل أسرع ، قد يكون من المنطقي نقل هذا النوع إلى مكتبة "مجموعات المجتمع" التي تتم مناقشتها في https://github.com/dotnet/runtime/discussions/42205

أوافق على أن الأولوية الصغرى أولاً هي الأفضل. تعد قائمة الانتظار ذات الأولوية مفيدة أيضًا في تطوير الألعاب القائمة على الدور ، وتكون القدرة على الحصول على الأولوية بمثابة عداد متزايد بشكل رتيب عندما يحصل كل عنصر على دوره التالي وهو أمر مفيد للغاية.

نحن نتطلع إلى تقديم هذا لمراجعة واجهة برمجة التطبيقات في أسرع وقت ممكن (على الرغم من أننا لسنا ملتزمين بعد بتقديم تنفيذ ضمن الإطار الزمني لـ .NET 6).

ولكن قبل أن نرسل هذا للمراجعة ، سنحتاج إلى تسوية بعض الأسئلة المفتوحة عالية المستوى. أعتقدTimLovellSmith الصورة الكتابة حتى هو جيد نقطة الانطلاق نحو تحقيق هذا الهدف.

بعض الملاحظات حول تلك النقاط:

  • الإجماع على الأسئلة 1-3 راسخ منذ زمن طويل ، وأعتقد أنه يمكننا التعامل مع هذه الأسئلة على أنها محلولة.

  • _استخدام المحدد (للأولوية المخزنة داخل القيمة) أو لا _ - متفق مع ملاحظاتك. هناك مشكلة أخرى في هذا التصميم وهي أن المحددات اختيارية ، مما يعني أنك بحاجة إلى توخي الحذر حتى لا تستدعي خطأ Enqueue الزائد (أو المخاطرة بالحصول على InvalidOperationException ).

  • أفضل استخدام tuple يزيد عن KeyValuePair . يبدو استخدام خاصية .Key للوصول إلى قيمة TPriority أمرًا غريبًا بالنسبة لي. يتيح لك tuple استخدام .Priority الذي يحتوي على خصائص توثيق ذاتي أفضل.

  • _هل يجب أن يكون لدى كل من Peek و Dequeue حجة بدلاً من tuple؟ _ - أعتقد أننا يجب أن نتبع العرف المتعارف عليه في طرق مماثلة في أماكن أخرى. لذلك ربما استخدم الوسيطة out .

  • _هل استخدام Peek and Dequeue مفيد على الإطلاق؟ _ - وافق بنسبة 100٪ مع تعليقاتك.

  • _أصغر أولوية أولاً هي الأفضل _ - متفق عليه.

  • _لا يجب أن يوفر التعداد أي ضمانات للطلب_ - هل هذا لا ينتهك توقعات المستخدم؟ ما هي المقايضات؟ ملحوظة: يمكننا على الأرجح تأجيل تفاصيل مثل هذه لمناقشة مراجعة واجهة برمجة التطبيقات.

أود أيضًا إعادة صياغة بعض الأسئلة المفتوحة الأخرى:

  • هل نقوم بشحن PriorityQueue<T> أو PriorityQueue<TElement, TPriority> أم كلاهما؟ - أنا شخصياً أعتقد أنه يجب علينا تنفيذ الخيار الأخير فقط ، لأنه يبدو أنه يوفر حلاً أنظف وأكثر عمومية. من حيث المبدأ ، يجب ألا تكون الأولوية خاصية متأصلة في عنصر قائمة الانتظار ، لذلك يجب ألا نجبر المستخدمين على تغليفها في أنواع المجمعات.

  • هل نطلب تفرد العنصر ، حتى المساواة؟ - صححني إذا كنت مخطئًا ، لكنني أشعر أن التفرد هو قيد مصطنع ، يفرض علينا متطلبات دعم سيناريوهات التحديث دون اللجوء إلى نهج التعامل. كما أنه يعقد سطح واجهة برمجة التطبيقات ، نظرًا لأننا الآن بحاجة أيضًا إلى القلق بشأن العناصر التي تحتوي على دلالات المساواة الصحيحة (ما هي المساواة المناسبة عندما تكون العناصر كبيرة DTOs؟). يمكنني رؤية ثلاثة مسارات ممكنة هنا:

    1. طلب التفرد / المساواة ودعم سيناريوهات التحديث بتمرير العنصر الأصلي (حتى المساواة).
    2. لا تتطلب التفرد / المساواة ودعم سيناريوهات التحديث باستخدام المقابض. يمكن الحصول عليها باستخدام متغيرات الطريقة الاختيارية Enqueue للمستخدمين الذين يحتاجون إليها. إذا كانت تخصيصات المقبض مصدر قلق كبير بما يكفي ، فأنا أتساءل عما إذا كان يمكن استهلاكها على غرار مهمة القيمة؟
    3. لا تتطلب التفرد / المساواة ولا تدعم تحديث الأولويات.
  • هل نؤيد دمج قوائم الانتظار؟ - يبدو أن الإجماع لا ، نظرًا لأننا نقوم بشحن تنفيذ واحد فقط (باستخدام أكوام المصفوفة على الأرجح) حيث لا يكون الدمج فعالاً.

  • ما هي الواجهات التي يجب أن تنفذها؟ - لقد رأيت بعض المقترحات التي توصي بـ IQueue<T> ، لكن هذا يبدو وكأنه تجريد متسرب. أنا شخصياً أفضل أن أبقيه بسيطًا وأن أقوم فقط بتطبيق ICollection<T> .

تضمين التغريدة

eiriktsarpalis على العكس من ذلك - لا يوجد شيء مصطنع بشأن القيد لدعم التحديثات على الإطلاق!

غالبًا ما تقوم الخوارزميات ذات قوائم الانتظار ذات الأولوية بتحديث أولويات العناصر. السؤال هو ، هل تقدم واجهة برمجة تطبيقات موجهة للكائنات ، والتي "تعمل فقط" لتحديث أولويات الكائنات العادية ... أم أنك تجبر الأشخاص على
أ) فهم نموذج المقبض
ب) الاحتفاظ ببيانات إضافية في الذاكرة ، مثل الخصائص الإضافية أو قاموس خارجي ، لتتبع مقابض الكائنات ، من جانبهم ، فقط حتى يتمكنوا من إجراء التحديثات (هل يجب أن يتماشى مع القاموس للفئات التي لا يمكنهم تغييرها؟ أو قم بترقية العناصر إلى مجموعات ، إلخ)
ج) الهياكل الخارجية "إدارة الذاكرة" أو "جمع القمامة" ، أي مقبض التنظيف للعناصر التي لم تعد في قائمة الانتظار ، عند استخدام نهج القاموس
د) عدم الخلط بين المقابض غير الشفافة في السياقات وقوائم الانتظار المتعددة لأنها ذات مغزى فقط في سياق قائمة انتظار ذات أولوية واحدة

بالإضافة إلى أن هناك هذا السؤال الفلسفي حول السبب الكامل الذي قد يجعل أي شخص يرغب في قائمة انتظار _ تحافظ على تتبع الكائنات حسب الأولوية _ على التصرف بهذه الطريقة: لماذا يجب أن يكون للكائن نفسه (يساوي إرجاع صحيح) أولويتين مختلفتين؟ إذا كان من المفترض حقًا أن يكون لديهم أولويات مختلفة ، فلماذا لا يتم تصميمهم على أنهم كائنات مختلفة ؟ (أو تم تحويلها إلى مجموعات ، مع مميّزات؟)

أيضًا بالنسبة للمقابض ، يجب أن يكون هناك جدول مقابض داخلي في قائمة انتظار الأولوية فقط حتى تعمل هذه المقابض بالفعل. أعتقد أن هذا عمل مكافئ للاحتفاظ بقاموس للبحث عن الكائنات في قائمة الانتظار.

ملاحظة: يدعم كل كائن. net بالفعل مفاهيم المساواة / التفرد ، لذلك ليس من المطلوب "طلب" ذلك.

بخصوص KeyValuePair ، أوافق على أنه ليس مثاليًا (على الرغم من أنه في الاقتراح ، Key للعنصر ، Value للأولوية ، والتي تتوافق مع Sorted المختلفة أنواع البيانات struct مخصصًا حسن الاسم على tuple في أي مكان على واجهة برمجة التطبيقات العامة ، خاصةً بالنسبة للإدخال.

فيما يتعلق بالتفرد ، هذا مصدر قلق أساسي ، ولا أعتقد أنه يمكن اتخاذ قرار بشأن أي شيء آخر حتى يتم ذلك. أفضّل تفرد العنصر كما هو محدد بواسطة المقارنة (الاختيارية) (وفقًا لكل من المقترحات الحالية والاقتراح الأول) إذا كان الهدف هو واجهة برمجة تطبيقات واحدة سهلة الاستخدام وذات غرض عام. الفريد مقابل غير الفريد هو صدع كبير ، وأنا أستخدم كلا النوعين لأغراض مختلفة. الأول "أصعب" في التنفيذ ، ويغطي معظم حالات الاستخدام (والأكثر نموذجية) (فقط تجربتي) مع صعوبة إساءة الاستخدام. حالات الاستخدام تلك التي _تتطلب_ عدم التفرد يجب أن تتم خدمتها (IMO) بنوع مختلف (على سبيل المثال ، كومة ثنائية قديمة بسيطة) ، وسأكون ممتنًا لوجود كليهما. هذا هو أساسًا ما يوفره الاقتراح الأصلي الذي تم ربطه بواسطة _تعديل: كلا ، لن يدعم ذلك تقييد الأولويات_

على العكس من ذلك - لا يوجد شيء مصطنع بشأن قيود دعم التحديثات على الإطلاق!

أنا آسف ، لم أقصد الإشارة إلى أن دعم التحديثات مصطنع ؛ بدلاً من ذلك ، يتم تقديم شرط التفرد بشكل مصطنع لدعم التحديثات.

ملاحظة: يدعم كل كائن. net بالفعل مفاهيم المساواة / التفرد ، لذلك ليس من المطلوب "طلب" ذلك

بالتأكيد ، ولكن في بعض الأحيان قد لا تكون دلالات المساواة التي تأتي مع النوع هي المرغوبة (مثل المساواة المرجعية ، والمساواة الهيكلية الكاملة ، وما إلى ذلك). أنا فقط أشير إلى أن المساواة صعبة وأن إجبارها على التصميم يجلب فئة جديدة كاملة من أخطاء المستخدم المحتملة.

eiriktsarpalis شكرًا لتوضيح ذلك. لكن هل هي حقا مصطنعة؟ أنا لا أعتقد أنه هو. انها مجرد حل طبيعي آخر.

يجب أن تكون واجهة برمجة التطبيقات _محددة جيدًا_. لا يمكنك توفير واجهة برمجة تطبيقات محدّثة دون مطالبة المستخدم بالتحديد بشأن ما يريد تحديثه بالضبط . المقابض والمساواة بين الكائنات هما فقط طريقتان مختلفتان لبناء واجهة برمجة التطبيقات المحددة جيدًا.

نهج المعالجة: في كل مرة تضيف فيها كائنًا إلى المجموعة ، يجب أن تعطيه "اسمًا" ، أي "مقبض" ، بحيث يمكنك الرجوع إلى هذا الكائن بالضبط دون غموض لاحقًا في المحادثة.

نهج تفرد الكائن: في كل مرة تضيف فيها كائنًا إلى المجموعة ، يجب أن يكون كائنًا مختلفًا ، أو يجب عليك تحديد كيفية التعامل مع الحالة الموجودة بالفعل.

لسوء الحظ ، فقط أسلوب الكائن يسمح لك حقًا بدعم بعض من أكثر خصائص أساليب واجهة برمجة التطبيقات عالية المستوى فائدة ، مثل EnqueueIfNotExists أو EnqueueOrDecreasePriroity (item) - لا معنى لتقديمها في تصميم يركز على المقبض ، لأنك لا يمكن معرفة ما إذا كان العنصر موجودًا بالفعل في قائمة الانتظار (نظرًا لأن وظيفتك تتبع ذلك ، باستخدام المقابض).

أحد الانتقادات الأكثر وضوحًا لنهج المقبض ، أو إسقاط قيود التفرد بالنسبة لي ، هو أنه يجعل جميع أنواع السيناريوهات ذات الأولوية القابلة للتحديث أكثر تعقيدًا في التنفيذ:

على سبيل المثال

  1. استخدام PriorityQueueبالنسبة إلى السلاسل التي تمثل الرسائل / المشاعر / العلامات / اسم المستخدم التي يتم التصويت عليها / تم تحديثها ، فإن القيم الفريدة لها أولوية متغيرة
  2. استخدام PriorityQueue، double> لطلب مجموعات فريدة [سواء كانت لها تغييرات ذات أولوية أم لا] - يجب تتبع المقابض الإضافية في مكان ما
  3. استخدام PriroityQueueلتحديد أولويات فهارس الرسم البياني ، أو معرفات كائنات قاعدة البيانات ، عليك الآن رش المقابض من خلال التنفيذ الخاص بك

ملاحظة

بالتأكيد ، ولكن في بعض الأحيان قد لا تكون دلالات المساواة التي تأتي مع النوع هي المرغوبة

يجب أن تكون هناك فتحات هروب ، مثل IEqualityComparer ، أو تحويل إلى نوع أكثر ثراءً.

شكرًا على التعليقات 🥳 سنقوم بتحديث الاقتراح خلال عطلة نهاية الأسبوع ، مع الأخذ في الاعتبار جميع المدخلات الجديدة ، ومشاركة مراجعة جديدة لجولة أخرى. ايتا 2020-09-20.

اقتراح قائمة انتظار الأولوية (الإصدار 2.0)

ملخص

يقترح مجتمع .NET Core أن يضيف إلى وظائف مكتبة النظام _priority queue_ ، وهي بنية بيانات يكون لكل عنصر فيها أولوية مرتبطة به. على وجه التحديد ، نقترح إضافة PriorityQueue<TElement, TPriority> إلى مساحة الاسم System.Collections.Generic .

العقيدة

في تصميمنا ، استرشدنا بالمبادئ التالية (ما لم تكن تعرف أفضل منها):

  • تغطية واسعة. نريد أن نقدم لعملاء .NET Core بنية بيانات قيّمة ومتعددة الاستخدامات بما يكفي لدعم مجموعة متنوعة من حالات الاستخدام.
  • تعلم من الأخطاء المعروفة. نحن نسعى جاهدين لتقديم وظيفة قائمة الانتظار ذات الأولوية التي ستكون خالية من المشكلات التي تواجه العملاء الموجودة في أطر العمل واللغات الأخرى ، مثل Java و Python و C ++ و Rust. سنتجنب اتخاذ خيارات تصميم يُعرف عنها أنها تجعل العملاء غير سعداء وتقليل فائدة قوائم الانتظار ذات الأولوية.
  • عناية فائقة مع قرارات الباب في اتجاه واحد. بمجرد تقديم API ، لا يمكن تعديلها أو حذفها ، بل توسيعها فقط. سنقوم بتحليل خيارات التصميم بعناية لتجنب الحلول دون المثلى التي سيظل عملاؤنا عالقين بها إلى الأبد.
  • تجنب شلل التصميم. نحن نقبل أنه قد لا يكون هناك حل مثالي. سنوازن بين المفاضلات والمضي قدمًا في التسليم ، لنوفر لعملائنا أخيرًا الوظائف التي كانوا ينتظرونها منذ سنوات.

خلفية

من وجهة نظر الزبون

من الناحية المفاهيمية ، فإن قائمة انتظار الأولوية هي مجموعة من العناصر ، حيث يكون لكل عنصر أولوية مرتبطة به. تتمثل الوظيفة الأكثر أهمية لقائمة الانتظار ذات الأولوية في أنها توفر وصولاً فعالاً إلى العنصر ذي الأولوية القصوى في المجموعة ، وخيارًا لإزالة هذا العنصر. قد يشمل السلوك المتوقع أيضًا: 1) القدرة على تعديل أولوية عنصر موجود بالفعل في المجموعة ؛ 2) القدرة على دمج قوائم انتظار متعددة ذات أولوية.

علوم الحاسوب الخلفية

قائمة انتظار الأولوية هي بنية بيانات مجردة ، أي أنها مفهوم له خصائص سلوكية معينة ، كما هو موضح في القسم السابق. تستند عمليات التنفيذ الأكثر كفاءة لقائمة انتظار الأولوية إلى أكوام. ومع ذلك ، على عكس المفهوم الخاطئ العام ، فإن الكومة هي أيضًا بنية بيانات مجردة ، ويمكن تحقيقها بطرق مختلفة ، كل منها يقدم مزايا وعيوب مختلفة.

معظم مهندسي البرمجيات على دراية فقط بتنفيذ الكومة الثنائية القائمة على المصفوفة - إنه أبسط واحد ، لكنه للأسف ليس الأكثر كفاءة. بالنسبة للإدخال العشوائي العام ، هناك مثالان لأنواع الكومة الأكثر كفاءة هما: الكومة الرباعية وكومة الاقتران . لمزيد من المعلومات حول الأكوام ، يرجى الرجوع إلى ويكيبيديا وهذه الورقة .

آلية التحديث هي التحدي الرئيسي في التصميم

أظهرت مناقشاتنا أن أكثر المجالات تحديًا في التصميم ، وفي نفس الوقت مع التأثير الأكبر على واجهة برمجة التطبيقات ، هي آلية التحديث. على وجه التحديد ، يتمثل التحدي في تحديد ما إذا كان المنتج الذي نرغب في تقديمه للعملاء يجب أن يدعم تحديث أولويات العناصر الموجودة بالفعل في المجموعة وكيفية ذلك.

هذه القدرة ضرورية لتنفيذ ، على سبيل المثال ، أقصر خوارزمية مسار Dijkstra أو جدولة الوظائف التي تحتاج إلى التعامل مع الأولويات المتغيرة. آلية التحديث مفقودة في Java ، والتي أظهرت أنها مخيبة للآمال للمهندسين ، على سبيل المثال في أسئلة StackOverflow الثلاثة التي تم عرضها أكثر من 32 ألف مرة: مثال ، مثال ، مثال . لتجنب تقديم API مع هذه القيمة المحدودة ، نعتقد أن أحد المتطلبات الأساسية لوظيفة قائمة الانتظار ذات الأولوية التي نقدمها سيكون دعم القدرة على تحديث الأولويات للعناصر الموجودة بالفعل في المجموعة.

لتقديم آلية التحديث ، يجب أن نتأكد من أن العميل يمكنه أن يكون محددًا بشأن ما يريد تحديثه بالضبط. لقد حددنا طريقتين لتقديم ذلك: أ) عبر مقابض. و ب) من خلال فرض تفرد العناصر في المجموعة. كل من هذه تأتي مع فوائد وتكاليف مختلفة.

الخيار (أ): مقابض. في هذا النهج ، في كل مرة يتم فيها إضافة عنصر إلى قائمة الانتظار ، توفر بنية البيانات مقبضها الفريد. إذا أراد العميل استخدام آلية التحديث ، فعليه تتبع هذه المقابض حتى يتمكن لاحقًا من تحديد العنصر الذي يرغب في تحديثه بشكل لا لبس فيه. التكلفة الأساسية لهذا الحل هي أن العملاء بحاجة إلى إدارة هذه المؤشرات. ومع ذلك ، هذا لا يعني أن هناك حاجة إلى أي تخصيصات داخلية لدعم المقابض ضمن قائمة انتظار الأولوية - أي كومة غير قائمة على المصفوفة تعتمد على العقد ، حيث تكون كل عقدة تلقائيًا مقبضها الخاص. كمثال ، راجع واجهة برمجة التطبيقات الخاصة بأسلوب PairingHeap.Update .

الخيار (ب): التفرد. يضع هذا النهج قيدين إضافيين على العميل: 1) يجب أن تتوافق العناصر الموجودة في قائمة انتظار الأولوية مع بعض دلالات المساواة ، مما يؤدي إلى ظهور فئة جديدة من أخطاء المستخدم المحتملة ؛ ب) لا يمكن تخزين عنصرين متساويين في نفس قائمة الانتظار. من خلال دفع هذه التكلفة ، نحصل على ميزة دعم آلية التحديث دون اللجوء إلى نهج المقبض. ومع ذلك ، فإن أي تنفيذ يعزز التفرد / المساواة لتحديد العنصر المراد تحديثه سيتطلب تعيينًا داخليًا إضافيًا ، بحيث يتم تنفيذه في O (1) وليس في O (n).

توصية

نوصي بإضافة فئة PriorityQueue<TElement, TPriority> إلى مكتبة النظام والتي تدعم آلية التحديث من خلال المقابض. سيكون التنفيذ الأساسي عبارة عن كومة إقران.

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

مثال على الاستخدام

1) العميل الذي لا يهتم بآلية التحديث

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) العميل الذي يهتم بآلية التحديث

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

التعليمات

في أي ترتيب تُعد قائمة انتظار الأولوية العناصر؟

بترتيب غير محدد ، بحيث يمكن أن يحدث التعداد في O (n) ، كما هو الحال مع HashSet . لن يوفر أي تطبيق فعال قدرات تعداد كومة في الوقت الخطي مع ضمان تعداد العناصر بالترتيب - وهذا يتطلب O (n log n). نظرًا لأن الطلب على مجموعة يمكن تحقيقه بشكل تافه باستخدام .OrderBy(x => x.Priority) ولا يهتم كل عميل بالتعداد باستخدام هذا الطلب ، فنحن نعتقد أنه من الأفضل تقديم أمر تعداد غير محدد.

الملاحق

الملحق أ: لغات أخرى مع وظيفة قائمة الانتظار ذات الأولوية

| اللغة | اكتب | ملاحظات |
|: -: |: -: |: -: |
| جافا | طابور الأولوية | لتوسيع فئة الملخص AbstractQueue وتنفيذ الواجهة Queue . |
| الصدأ | BinaryHeap | |
| سويفت | CFBinaryHeap | |
| C ++ | Priority_queue | |
| بايثون | heapq | |
| اذهب | كومة | هناك واجهة كومة. |

الملحق ب: قابلية الاكتشاف

لاحظ أنه عند مناقشة هياكل البيانات ، يتم استخدام المصطلح _heap_ بمعدل 4 مرات أكثر من مصطلح _priority queue_.

  • "array" AND "data structure" - نتائج 17.400.000
  • "stack" AND "data structure" - 12.100.000 نتيجة
  • "queue" AND "data structure" - 3.850.000 نتيجة
  • "heap" AND "data structure" - 1.830.000 نتيجة
  • "priority queue" AND "data structure" - 430.000 نتيجة
  • "trie" AND "data structure" - 335.000 نتيجة

يرجى المراجعة ، ويسعدني تلقي التعليقات والاستمرار في التكرار :) أشعر أننا نتقارب! 😄 يسعدني أيضًا تلقي الأسئلة - سأضيفها إلى الأسئلة الشائعة!

pgolebiowski لا أميل إلى استخدام واجهات برمجة التطبيقات (API) القائمة على بتغليف هذا كما في المثال 2 إذا كنت أقوم بإعادة تعديل أي رمز موجود) ولكنه يبدو جيدًا بالنسبة لي في الغالب. قد أذهب مع التنفيذ الذي ذكرته لأرى كيف يبدو الأمر ، ولكن إليك بعض التعليقات خارج الخفاش:

  • يبدو من الغريب أن يقوم TryDequeue بإرجاع عقدة ، لأنه لا يمثل حقًا مؤشرًا في هذه المرحلة (أفضل معلمتين منفصلتين)
  • هل سيكون مستقرًا من حيث أنه إذا تم وضع عنصرين في قائمة الانتظار بنفس الأولوية التي يتم وضعهما في قائمة انتظارهما بترتيب يمكن التنبؤ به؟ (من الجيد امتلاكه من أجل التكاثر ؛ يمكن تنفيذه بسهولة كافية من قبل المستهلك بخلاف ذلك)
  • يجب أن تكون معلمة Merge PriorityQueue'2 وليس PriorityQueueNode'2 ، وهل يمكنك توضيح السلوك؟ لست معتادًا على كومة الاقتران ، لكن من المفترض أن الكومة تتداخل بعد ذلك
  • لست من المعجبين بالاسم Contains للطريقة المكونة من معلمتين: هذا ليس اسمًا أعتقد أنه أسلوب نمط TryGet
  • هل يجب أن يدعم الفصل IEqualityComparer<TElement> مخصصًا لغرض Contains ؟
  • لا يبدو أن هناك طريقة لتحديد ما إذا كانت العقدة لا تزال في الكومة بكفاءة (لست متأكدًا من أنني سأستخدم هذا ، على ما أعتقد)
  • من الغريب أن يكون إرجاع Remove bool ؛ كنت أتوقع أنه كان يسمى TryRemove أو أنه رمى (والذي أفترض أن Update يفعله إذا لم تكن العقدة في الكومة).

VisualMelon شكرًا جزيلاً على التعليقات! سنحل هذه المشكلات بسرعة ، وأوافق بالتأكيد:

  • يبدو من الغريب أن يقوم TryDequeue بإرجاع عقدة ، لأنه لا يمثل حقًا مؤشرًا في هذه المرحلة (أفضل معلمتين منفصلتين)
  • يجب أن تكون معلمة Merge PriorityQueue'2 وليس PriorityQueueNode'2 ، وهل يمكنك توضيح السلوك؟ لست معتادًا على كومة الاقتران ، لكن من المفترض أن الكومة تتداخل بعد ذلك
  • من الغريب أن Remove المرتجعات هو bool ؛ كنت أتوقع أنه كان يطلق عليه TryRemove أو أنه رمى (والذي أفترض أن Update يفعله إذا لم تكن العقدة في الكومة).
  • لست من المعجبين بالاسم Contains للطريقة المكونة من معلمتين: هذا ليس اسمًا أعتقده لطريقة النمط TryGet

توضيح لهذين:

  • هل سيكون مستقرًا من حيث أنه إذا تم وضع عنصرين في قائمة الانتظار بنفس الأولوية التي يتم وضعهما في قائمة انتظارهما بترتيب يمكن التنبؤ به؟ (من الجيد امتلاكه من أجل التكاثر ؛ يمكن تنفيذه بسهولة كافية من قبل المستهلك بخلاف ذلك)
  • لا يبدو أن هناك طريقة لتحديد ما إذا كانت العقدة لا تزال في الكومة بكفاءة (لست متأكدًا من أنني سأستخدم هذا ، على ما أعتقد)

بالنسبة للنقطة الأولى ، إذا كان الهدف هو إعادة الإنتاج ، فسيكون التنفيذ حتميًا ، نعم. إذا كان الهدف _ إذا كان هناك عنصران تم وضعهما في قائمة الانتظار بنفس الأولوية ، فيتبع ذلك أنهما سيخرجان بنفس الترتيب الذي تم وضعهما به في قائمة الانتظار _ - لست متأكدًا مما إذا كان يمكن تعديل التنفيذ إلى لتحقيق هذه الخاصية ، فإن الإجابة السريعة ستكون "على الأرجح لا".

بالنسبة للنقطة الثانية ، نعم ، الأكوام ليست جيدة للتحقق مما إذا كان العنصر موجودًا في المجموعة ، فسيتعين على العميل تتبع ذلك بشكل منفصل لتحقيق ذلك في O (1) أو إعادة استخدام التعيين الذي يستخدمه للمقابض ، إذا يستخدمون آلية التحديث. خلاف ذلك ، O (n).

  • هل يجب أن يدعم الفصل IEqualityComparer<TElement> مخصصًا لغرض Contains ؟

حسنًا ... لقد بدأت أعتقد أنه ربما يكون وضع Contains في مسؤولية قائمة انتظار الأولوية هذه أكثر من اللازم ، واستخدام الطريقة من Linq قد يكون كافياً ( Contains يجب تطبيقه على التعداد على أي حال).

pgolebiowski شكرا على التوضيحات

بالنسبة للنقطة الأولى ، إذا كان الهدف هو إعادة الإنتاج ، فسيكون التنفيذ حتميًا ، نعم

ليست حتمية بقدر ما هي مضمونة إلى الأبد (على سبيل المثال ، حتى التغييرات في التطبيق ، والسلوك لا) ، لذلك أعتقد أن الإجابة التي كنت أسعى وراءها هي "لا". هذا جيد: يمكن للمستهلك إضافة معرف تسلسل إلى الأولوية إذا احتاج إلى ذلك ، على الرغم من أن SortedSet سيؤدي المهمة في هذه المرحلة.

بالنسبة للنقطة الثانية ، نعم ، الأكوام ليست جيدة للتحقق مما إذا كان العنصر موجودًا في المجموعة ، فسيتعين على العميل تتبع ذلك بشكل منفصل لتحقيق ذلك في O (1) أو إعادة استخدام التعيين الذي يستخدمه للمقابض ، إذا يستخدمون آلية التحديث. خلاف ذلك ، O (n).

ألا يتطلب مجموعة فرعية من العمل اللازم لـ Remove ؟ ربما لم أكن واضحًا: لقد قصدت إعطاء PriorityQueueNode ، التحقق مما إذا كان في الكومة (وليس TElement ).

حسنًا ... بدأت أعتقد أنه ربما يكون وضع محتويات ضمن مسؤولية قائمة انتظار الأولوية هذه أكثر من اللازم

لن أشتكي إذا لم يكن Contains موجودًا: إنه أيضًا مصيدة للأشخاص الذين لا يدركون أنه يجب عليهم استخدام المقابض.

pgolebiowski يبدو أنك تفضل المقابض بقوة ،

من وجهة نظر الكفاءة ، أظن أن المقابض هي الأفضل حقًا ، لكل من السيناريوهين مع التفرد وبدون ذلك ، لذلك أنا موافق على ذلك باعتباره الحل الأساسي الذي يقدمه إطار العمل.

على الاطلاق:
من وجهة نظر قابلية الاستخدام ، أعتقد أن العناصر المكررة نادرًا ما تكون ما أريده ، الأمر الذي لا يزال يتركني أتساءل عما إذا كان من المفيد لإطار العمل تقديم الدعم لكلا النموذجين. ولكن ... سيكون من السهل على الأقل إضافة فئة "PrioritySet" ذات عنصر فريد من نوعه في وقت لاحق كغلاف لـ PriorityQueue المقترحة ، على سبيل المثال استجابة للطلب المستمر لواجهة برمجة تطبيقات أكثر ودية. (إذا كان الطلب موجودًا. كما أعتقد أنه قد يكون!)

بالنسبة لواجهة برمجة التطبيقات الحالية المقترحة نفسها ، هناك بعض الأفكار / الأسئلة:

  • ماذا عن تقديم عبء زائد قدره TryPeek(out TElement element, out TPriority priority) ؟
  • إذا كان لديك مفاتيح مكررة قابلة للتحديث ، فإنني أشعر بالقلق من أنه إذا لم تعيد "dequeue" عقدة قائمة الانتظار ذات الأولوية ، فكيف يمكنك التحقق من إزالة العقدة الصحيحة الصحيحة من نظام تتبع المقبض الخاص بك؟ نظرًا لأنه يمكن أن يكون لديك أكثر من نسخة واحدة من العنصر بنفس الأولوية.
  • هل يتم طرح Remove(PriorityQueueNode) إذا لم يتم العثور على العقدة؟ أو العودة كاذبة؟
  • هل يجب أن يكون هناك إصدار TryRemove() لا يتم طرحه في حالة إزالة الرميات؟
  • لست متأكدًا من أن واجهة برمجة تطبيقات Contains() مفيدة معظم الوقت؟ يبدو أن "يحتوي" على ما يبدو في عين الناظر ، خاصة بالنسبة للسيناريوهات التي تحتوي على عناصر "مكررة" ذات أولويات مميزة ، أو ميزات أخرى مميزة! في هذه الحالة ، ربما يتعين على المستخدم النهائي إجراء البحث الخاص به على أي حال. ولكن على الأقل يمكن أن يكون مفيدًا للسيناريو بدون تكرارات.

pgolebiowski نشكرك على الوقت الذي

  • ترديدًا لتعليقTimLovellSmith ، لست متأكدًا من ضرورة وجود Contains() أو TryGetNode() على الإطلاق في واجهة برمجة التطبيقات بصيغتها المقترحة حاليًا. إنها تشير إلى أن المساواة لـ TElement أمر مهم ، وهو ما يفترض أنه أحد الأشياء التي كان النهج القائم على المقبض يحاول تجنبها.
  • ربما أعيد صياغة public void Dequeue(out TElement element); كـ public TElement Dequeue();
  • لماذا تحتاج طريقة TryDequeue() لإرجاع الأولوية؟
  • ألا يجب أن يطبق الفصل أيضًا ICollection<T> أو IReadOnlyCollection<T> ؟
  • ماذا يجب أن يحدث إذا حاولت تحديث PriorityQueueNode الذي تم إرجاعه من مثيل PriorityQueue مختلف؟
  • هل نريد دعم عملية دمج فعالة؟ يعني هذا AFAICT أننا لا نستطيع استخدام التمثيل القائم على مصفوفة. كيف سيؤثر ذلك على التنفيذ من حيث المخصصات؟

معظم مهندسي البرمجيات على دراية فقط بتنفيذ الكومة الثنائية القائمة على المصفوفة - إنه أبسط واحد ، لكنه للأسف ليس الأكثر كفاءة. بالنسبة للإدخال العشوائي العام ، هناك مثالان لأنواع الكومة الأكثر كفاءة هما: الكومة الرباعية وكومة الاقتران.

ما هي المفاضلات لاختيار النهج الأخير؟ هل من الممكن استخدام تطبيق قائم على مصفوفة لهؤلاء؟

لا يمكنها تنفيذ ICollection<T> أو IReadOnlyCollection<T> عندما لا تحتوي على التوقيعات الصحيحة لـ "إضافة" و "إزالة" وما إلى ذلك.

إنه أقرب بكثير في الروح / الشكل إلى ICollection<KeyValuePair<T,Priority>>

إنه أقرب بكثير في الروح / الشكل إلى ICollection<KeyValuePair<T,Priority>>

ألن يكون الأمر KeyValuePair<TPriority, TElement> ، نظرًا لأن الطلب يتم بواسطة TPriority ، وهي آلية مفتاح فعالة؟

حسنًا ، يبدو أننا نؤيد بشكل عام إسقاط الأساليب Contains و TryGet . سيتم إزالتها في المراجعة التالية وشرح سبب الإزالة في الأسئلة الشائعة.

بالنسبة للواجهات المطبقة - ألا يكفي IEnumerable<PriorityQueueNode<TElement, TPriority>> ؟ ما نوع الوظيفة المفقودة؟

في KeyValuePair - كان هناك عدد قليل من الأصوات التي يفضلها أكثر من tuple أو Struct مع .Element و .Priority . أعتقد أنني أؤيد هذه.

ألن يكون الأمر KeyValuePair<TPriority, TElement> ، نظرًا لأن الطلب يتم بواسطة TPriority ، وهي آلية مفتاح فعالة؟

هناك حجج جيدة لكلا الجانبين. من ناحية ، نعم ، بالضبط ما قلته للتو. من ناحية أخرى ، من المتوقع عمومًا أن تكون المفاتيح في مجموعة KVP فريدة ، ومن الصحيح تمامًا أن تحتوي على عناصر متعددة لها نفس الأولوية.

من ناحية أخرى ، من المتوقع عمومًا أن تكون المفاتيح في مجموعة KVP فريدة

أنا لا أتفق مع هذا البيان - مجموعة من أزواج المفتاح والقيمة هي بالضبط ؛ يتم وضع أي متطلبات تفرد فوق ذلك.

أليس IEnumerable<PriorityQueueNode<TElement, TPriority>> كافياً؟ ما نوع الوظيفة المفقودة؟

أتوقع شخصيًا أن يتم تنفيذ IReadOnlyCollection<PQN<TElement, TPriority>> ، نظرًا لأن واجهة برمجة التطبيقات المقدمة تلبي بالفعل هذه الواجهة في الغالب. بالإضافة إلى ذلك ، سيكون ذلك متسقًا مع أنواع المجموعات الأخرى.

بخصوص الواجهة:

""
منطقي عام TryGetNode (عنصر TElement ، خارج PriorityQueueNodeالعقدة)؛ // تشغيل)

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

public PriorityQueue (IEnumerable> جمع) ؛
""

أود أيضًا أن يقوم PriorityQueue بتنفيذ واجهة IReadonlyCollection<> .

القفز إلى أشياء أخرى غير سطح API العام.

هذه القدرة ضرورية لتنفيذ ، على سبيل المثال ، أقصر خوارزمية مسار Dijkstra أو جدولة الوظائف التي تحتاج إلى التعامل مع الأولويات المتغيرة. آلية التحديث مفقودة في Java ، والتي أظهرت أنها مخيبة للآمال للمهندسين ، على سبيل المثال في أسئلة StackOverflow الثلاثة التي تم عرضها أكثر من 32 ألف مرة: مثال ، مثال ، مثال. لتجنب تقديم API مع هذه القيمة المحدودة ، نعتقد أن أحد المتطلبات الأساسية لوظيفة قائمة الانتظار ذات الأولوية التي نقدمها سيكون دعم القدرة على تحديث الأولويات للعناصر الموجودة بالفعل في المجموعة.

أود أن أختلف. آخر مرة كتبت فيها Dijkstra في C ++ std :: priority_queue كانت كافية ولا تتعامل مع التحديث ذي الأولوية. الإجماع المشترك لـ AFAIK لهذه الحالة هو إضافة عنصر مزيف إلى قائمة الانتظار مع تغيير الأولوية والقيمة والمراقبة ، هل نقوم بمعالجة القيمة أم لا. يمكن فعل الشيء نفسه مع جدولة العمل.

لأكون صادقًا ، لست متأكدًا من الشكل الذي سيبدو عليه Dijkstra مع اقتراح قائمة الانتظار الحالية. كيف أفترض أن أتتبع العقد التي أحتاج إلى تحديث الأولوية؟ مع TryGetNode ()؟ أو لديك مجموعة أخرى من العقد؟ أود أن أرى رمز الاقتراح الحالي.

إذا نظرت إلى ويكيبيديا ، لا يوجد افتراض لتحديث الأولويات لقائمة الانتظار ذات الأولوية. نفس الشيء بالنسبة لجميع اللغات الأخرى التي لا تحتوي على هذه الوظيفة وتفلت من ذلك. أعرف أن "السعي إلى الأفضل" ، ولكن هل هناك طلب فعلي على ذلك؟

بالنسبة للإدخال العشوائي العام ، هناك مثالان لأنواع الكومة الأكثر كفاءة هما: الكومة الرباعية وكومة الاقتران. لمزيد من المعلومات حول الأكوام ، يرجى الرجوع إلى ويكيبيديا وهذه الورقة.

لقد بحثت في الورق وهذا اقتباس منه:

تظهر النتائج أن الاختيار الأمثل للتنفيذ يعتمد بشدة على المدخلات. علاوة على ذلك ، فإنه يوضح أنه يجب توخي الحذر لتحسين أداء ذاكرة التخزين المؤقت ، في المقام الأول عند الحاجز L1-L2. يشير هذا إلى أنه من غير المرجح أن تؤدي الهياكل المعقدة التي لا تراعي ذاكرة التخزين المؤقت أداءً جيدًا مقارنةً بالبنى الأبسط التي تدرك ذاكرة التخزين المؤقت.

بناءً على ما يبدو ، سيتم قفل اقتراح قائمة الانتظار الحالية وراء تنفيذ الشجرة بدلاً من المصفوفة ، ويخبرني شيء ما عن احتمال ألا تكون العقد الشجرية المنتشرة حول الذاكرة فعالة مثل مجموعة العناصر.

أعتقد أن وجود معايير لمقارنة كومة ثنائية بسيطة على أساس المصفوفة وكومة الاقتران ستكون مثالية لاتخاذ القرار المناسب ، قبل ذلك ، لا أعتقد أنه من الذكاء قفل التصميم وراء تنفيذ معين (أنا أبحث عنك Merge طريقة

الانتقال إلى موضوع آخر ، أنا شخصياً أفضل أن يكون لدي KeyValuePairمن فئة مخصصة جديدة.

  • سطح API أقل
  • يمكنني أن أفعل شيئًا كهذا: `` PriorityQueue جديد(قاموس جديد() {{1 ، 1} ، {2 ، 2} ، {3 ، 3} ، {4 ، 4} ، {5 ، 5}}) ؛ أعلم أنه محدود بسبب تفرد المفتاح. كما أعتقد أنه سيجلب تآزرًا جيدًا لاستهلاك المجموعات القائمة على IDictionary.
  • إنها بنية وليست فئة لذا لا توجد استثناءات لـ NullReference
  • في مرحلة ما ، سيحتاج PrioirtyQueue إلى إجراء تسلسل / إلغاء تسلسل وأعتقد أنه سيكون من الأسهل القيام بما هو موجود بالفعل.

الجانب السلبي هو المشاعر المختلطة التي تبحث عن TPriority Key لكني أعتقد أن التوثيق الجيد يمكن أن يحل ذلك.
"

كحل بديل لـ Dijkstra ، فإن إضافة عناصر وهمية إلى أعمال قائمة الانتظار ، ولا يتغير العدد الإجمالي لحواف الرسم البياني التي تعالجها. لكن عدد العقد المؤقتة التي تبقى مقيمة في الكومة الناتجة عن معالجة الحواف تتغير ، ويمكن أن يؤثر ذلك على استخدام الذاكرة وكفاءة قائمة الانتظار وإلغاء الصف.

كنت مخطئًا بشأن عدم قدرتي على إجراء IReadOnlyCollection ، فلا بأس بذلك. لا أضف () وأزل () في تلك الواجهة ، هاه! (بماذا كنت أفكر ...)

@ Ivanidzo4ka تعليقاتك تقنعني أكثر أنه سيكون من المنطقي وجود نوعين منفصلين: أحدهما عبارة عن كومة ثنائية بسيطة (أي بدون تحديث) ، والثاني كما هو موضح في أحد المقترحات:

  • تعد Binary Heaps بسيطة وسهلة التنفيذ ولديها واجهة برمجة تطبيقات صغيرة ، ومن المعروف أنها تؤدي أداءً جيدًا في الممارسة العملية في العديد من السيناريوهات المهمة.
  • توفر قائمة انتظار الأولوية الكاملة مزايا نظرية لبعض السيناريوهات (على سبيل المثال ، تقليل تعقيد المساحة ، وتقليل التعقيد الزمني للبحث في الرسوم البيانية كثيفة الاتصال) ، وتوفر واجهة برمجة تطبيقات طبيعية للعديد من الخوارزميات / السيناريوهات.

في الماضي ، كنت أفلت غالبًا من كومة ثنائية كتبتها أفضل جزء من عقد مضى ، ومجموعة SortedSet / Dictionary ، وهناك سيناريوهات استخدمت فيها كلا النوعين في أدوار منفصلة (KSSP ينبض إلى الذهن).

إنها بنية وليست فئة لذا لا توجد استثناءات لـ NullReference

سأجادل بأن الأمر هو العكس: إذا قام شخص ما بتمرير القيم الافتراضية حولها ، فأنا أحب أن يرى NRE ؛ ومع ذلك ، أعتقد أننا بحاجة إلى أن نكون واضحين حيث نتوقع استخدام هذه الأشياء: من المحتمل أن تكون العقد / المقابض فئات ، ولكن إذا كنا نقرأ / نعيد زوجًا فقط ، فأنا أوافق على أنه يجب أن يكون هيكلًا.


إنني أميل إلى اقتراح أنه ينبغي أن يكون من الممكن تحديث العنصر بالإضافة إلى الأولوية في الاقتراح القائم على المقابض. يمكنك تحقيق نفس التأثير بمجرد إزالة المقبض وإضافة مقبض جديد ، لكنها عملية مفيدة ، وقد يكون لها فوائد في الأداء اعتمادًا على التنفيذ (على سبيل المثال ، يمكن لبعض الأكوام أن تقلل من أولوية شيء رخيص نسبيًا). مثل هذا التغيير من شأنه أن يجعل تنفيذ العديد من الأشياء أكثر تنظيمًا (على سبيل المثال ، هذا المثال المحرض للكابوس إلى حد ما استنادًا إلى AlgoKit ParingHeap الحالي) ، خاصة تلك التي تعمل في منطقة غير معروفة من مساحة الولاية.

اقتراح قائمة انتظار الأولوية (الإصدار 2.1)

ملخص

يقترح مجتمع .NET Core أن يضيف إلى وظائف مكتبة النظام _priority queue_ ، وهي بنية بيانات يكون لكل عنصر فيها أولوية مرتبطة به. على وجه التحديد ، نقترح إضافة PriorityQueue<TElement, TPriority> إلى مساحة الاسم System.Collections.Generic .

العقيدة

في تصميمنا ، استرشدنا بالمبادئ التالية (ما لم تكن تعرف أفضل منها):

  • تغطية واسعة. نريد أن نقدم لعملاء .NET Core بنية بيانات قيّمة ومتعددة الاستخدامات بما يكفي لدعم مجموعة متنوعة من حالات الاستخدام.
  • تعلم من الأخطاء المعروفة. نحن نسعى جاهدين لتقديم وظيفة قائمة الانتظار ذات الأولوية التي ستكون خالية من المشكلات التي تواجه العملاء الموجودة في أطر العمل واللغات الأخرى ، مثل Java و Python و C ++ و Rust. سنتجنب اتخاذ خيارات تصميم يُعرف عنها أنها تجعل العملاء غير سعداء وتقليل فائدة قوائم الانتظار ذات الأولوية.
  • عناية فائقة مع قرارات الباب في اتجاه واحد. بمجرد تقديم API ، لا يمكن تعديلها أو حذفها ، بل توسيعها فقط. سنقوم بتحليل خيارات التصميم بعناية لتجنب الحلول دون المثلى التي سيظل عملاؤنا عالقين بها إلى الأبد.
  • تجنب شلل التصميم. نحن نقبل أنه قد لا يكون هناك حل مثالي. سنوازن بين المفاضلات والمضي قدمًا في التسليم ، لنوفر لعملائنا أخيرًا الوظائف التي كانوا ينتظرونها منذ سنوات.

خلفية

من وجهة نظر الزبون

من الناحية المفاهيمية ، فإن قائمة انتظار الأولوية هي مجموعة من العناصر ، حيث يكون لكل عنصر أولوية مرتبطة به. تتمثل الوظيفة الأكثر أهمية لقائمة الانتظار ذات الأولوية في أنها توفر وصولاً فعالاً إلى العنصر ذي الأولوية القصوى في المجموعة ، وخيارًا لإزالة هذا العنصر. قد يشمل السلوك المتوقع أيضًا: 1) القدرة على تعديل أولوية عنصر موجود بالفعل في المجموعة ؛ 2) القدرة على دمج قوائم انتظار متعددة ذات أولوية.

علوم الحاسوب الخلفية

قائمة انتظار الأولوية هي بنية بيانات مجردة ، أي أنها مفهوم له خصائص سلوكية معينة ، كما هو موضح في القسم السابق. تستند عمليات التنفيذ الأكثر كفاءة لقائمة انتظار الأولوية إلى أكوام. ومع ذلك ، على عكس المفهوم الخاطئ العام ، فإن الكومة هي أيضًا بنية بيانات مجردة ، ويمكن تحقيقها بطرق مختلفة ، كل منها يقدم مزايا وعيوب مختلفة.

معظم مهندسي البرمجيات على دراية فقط بتنفيذ الكومة الثنائية القائمة على المصفوفة - إنه أبسط واحد ، لكنه للأسف ليس الأكثر كفاءة. بالنسبة للإدخال العشوائي العام ، هناك مثالان لأنواع الكومة الأكثر كفاءة هما: الكومة الرباعية وكومة الاقتران . لمزيد من المعلومات حول الأكوام ، يرجى الرجوع إلى ويكيبيديا وهذه الورقة .

آلية التحديث هي التحدي الرئيسي في التصميم

أظهرت مناقشاتنا أن أكثر المجالات تحديًا في التصميم ، وفي نفس الوقت مع التأثير الأكبر على واجهة برمجة التطبيقات ، هي آلية التحديث. على وجه التحديد ، يتمثل التحدي في تحديد ما إذا كان المنتج الذي نرغب في تقديمه للعملاء يجب أن يدعم تحديث أولويات العناصر الموجودة بالفعل في المجموعة وكيفية ذلك.

هذه القدرة ضرورية لتنفيذ ، على سبيل المثال ، أقصر خوارزمية مسار Dijkstra أو جدولة الوظائف التي تحتاج إلى التعامل مع الأولويات المتغيرة. آلية التحديث مفقودة في Java ، والتي أظهرت أنها مخيبة للآمال للمهندسين ، على سبيل المثال في أسئلة StackOverflow الثلاثة التي تم عرضها أكثر من 32 ألف مرة: مثال ، مثال ، مثال . لتجنب تقديم API مع هذه القيمة المحدودة ، نعتقد أن أحد المتطلبات الأساسية لوظيفة قائمة الانتظار ذات الأولوية التي نقدمها سيكون دعم القدرة على تحديث الأولويات للعناصر الموجودة بالفعل في المجموعة.

لتقديم آلية التحديث ، يجب أن نتأكد من أن العميل يمكنه أن يكون محددًا بشأن ما يريد تحديثه بالضبط. لقد حددنا طريقتين لتقديم ذلك: أ) عبر مقابض. و ب) من خلال فرض تفرد العناصر في المجموعة. كل من هذه تأتي مع فوائد وتكاليف مختلفة.

الخيار (أ): مقابض. في هذا النهج ، في كل مرة يتم فيها إضافة عنصر إلى قائمة الانتظار ، توفر بنية البيانات مقبضها الفريد. إذا أراد العميل استخدام آلية التحديث ، فعليه تتبع هذه المقابض حتى يتمكن لاحقًا من تحديد العنصر الذي يرغب في تحديثه بشكل لا لبس فيه. التكلفة الأساسية لهذا الحل هي أن العملاء بحاجة إلى إدارة هذه المؤشرات. ومع ذلك ، هذا لا يعني أن هناك حاجة إلى أي تخصيصات داخلية لدعم المقابض ضمن قائمة انتظار الأولوية - أي كومة غير قائمة على المصفوفة تعتمد على العقد ، حيث تكون كل عقدة تلقائيًا مقبضها الخاص. كمثال ، راجع واجهة برمجة التطبيقات الخاصة بأسلوب PairingHeap.Update .

الخيار (ب): التفرد. يضع هذا النهج قيدين إضافيين على العميل: 1) يجب أن تتوافق العناصر الموجودة في قائمة انتظار الأولوية مع بعض دلالات المساواة ، مما يؤدي إلى ظهور فئة جديدة من أخطاء المستخدم المحتملة ؛ ب) لا يمكن تخزين عنصرين متساويين في نفس قائمة الانتظار. من خلال دفع هذه التكلفة ، نحصل على ميزة دعم آلية التحديث دون اللجوء إلى نهج المقبض. ومع ذلك ، فإن أي تنفيذ يعزز التفرد / المساواة لتحديد العنصر المراد تحديثه سيتطلب تعيينًا داخليًا إضافيًا ، بحيث يتم تنفيذه في O (1) وليس في O (n).

توصية

نوصي بإضافة فئة PriorityQueue<TElement, TPriority> إلى مكتبة النظام والتي تدعم آلية التحديث من خلال المقابض. سيكون التنفيذ الأساسي عبارة عن كومة إقران.

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

مثال على الاستخدام

1) العميل الذي لا يهتم بآلية التحديث

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) العميل الذي يهتم بآلية التحديث

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

التعليمات

1. بأي ترتيب تُعد قائمة انتظار الأولوية العناصر؟

بترتيب غير محدد ، بحيث يمكن أن يحدث التعداد في O (n) ، كما هو الحال مع HashSet . لن يوفر أي تطبيق فعال قدرات تعداد كومة في الوقت الخطي مع ضمان تعداد العناصر بالترتيب - وهذا يتطلب O (n log n). نظرًا لأن الطلب على مجموعة يمكن تحقيقه بشكل تافه باستخدام .OrderBy(x => x.Priority) ولا يهتم كل عميل بالتعداد باستخدام هذا الطلب ، فنحن نعتقد أنه من الأفضل تقديم أمر تعداد غير محدد.

2. لماذا لا توجد طريقة Contains أو TryGet ؟

توفير مثل هذه الأساليب له قيمة ضئيلة ، لأن العثور على عنصر في كومة يتطلب تعداد المجموعة بأكملها ، مما يعني أن أي طريقة Contains أو TryGet ستكون غلافًا حول التعداد. علاوة على ذلك ، للتحقق مما إذا كان العنصر موجودًا في المجموعة ، يجب أن تكون قائمة انتظار الأولوية على دراية بكيفية إجراء فحوصات المساواة للكائنات TElement ، وهو شيء لا نعتقد أنه يجب أن يقع ضمن مسؤولية قائمة انتظار الأولوية.

3. لماذا توجد حمولات زائدة Dequeue و TryDequeue تعيد PriorityQueueNode ؟

هذا مخصص للعملاء الذين يرغبون في استخدام طريقة Update أو Remove وتتبع المقابض. أثناء فصل عنصر من قائمة انتظار الأولوية ، سيحصلون على مقبض يمكنهم استخدامه لضبط حالة نظام تتبع المقبض.

4. ماذا يحدث عندما تتلقى الطريقة Update أو Remove عقدة من قائمة انتظار مختلفة؟

ستطرح قائمة انتظار الأولوية استثناءً. كل عقدة على علم بقائمة انتظار الأولوية التي تنتمي إليها ، وبالمثل فإن LinkedListNode<T> على علم بـ LinkedList<T> الذي تنتمي إليه.

الملاحق

الملحق أ: لغات أخرى مع وظيفة قائمة الانتظار ذات الأولوية

| اللغة | اكتب | ملاحظات |
|: -: |: -: |: -: |
| جافا | طابور الأولوية | لتوسيع فئة الملخص AbstractQueue وتنفيذ الواجهة Queue . |
| الصدأ | BinaryHeap | |
| سويفت | CFBinaryHeap | |
| C ++ | Priority_queue | |
| بايثون | heapq | |
| اذهب | كومة | هناك واجهة كومة. |

الملحق ب: قابلية الاكتشاف

لاحظ أنه عند مناقشة هياكل البيانات ، يتم استخدام المصطلح _heap_ بمعدل 4 مرات أكثر من مصطلح _priority queue_.

  • "array" AND "data structure" - 17.400.000 نتيجة
  • "stack" AND "data structure" - 12.100.000 نتيجة
  • "queue" AND "data structure" - 3.850.000 نتائج
  • "heap" AND "data structure" - نتائج 1.830.000
  • "priority queue" AND "data structure" - 430.000 نتيجة
  • "trie" AND "data structure" - 335.000 نتيجة

شكرا لكم جميعا على ردود الفعل! لقد قمت بتحديث الاقتراح إلى v2.1. التغيير:

  • تمت إزالة الطرق Contains و TryGet .
  • تمت إضافة الأسئلة الشائعة رقم 2: _لماذا لا توجد طريقة Contains أو TryGet ؟ _
  • تمت إضافة الواجهة IReadOnlyCollection<PriorityQueueNode<TElement, TPriority>> .
  • تمت إضافة حمل زائد bool TryPeek(out TElement element, out TPriority priority) .
  • تمت إضافة عبء زائد bool TryPeek(out TElement element) .
  • تمت إضافة عبء زائد قدره void Dequeue(out TElement element, out TPriority priority) .
  • تم تغيير void Dequeue(out TElement element) إلى PriorityQueueNode<TElement, TPriority> Dequeue() .
  • تمت إضافة عبء زائد bool TryDequeue(out TElement element) .
  • تمت إضافة عبء زائد bool TryDequeue(out PriorityQueueNode<TElement, TPriority> node) .
  • تمت إضافة الأسئلة الشائعة رقم 3: _لماذا هناك حمولات زائدة Dequeue و TryDequeue تعيد PriorityQueueNode ؟ _
  • تمت إضافة الأسئلة الشائعة رقم 4: _ماذا يحدث عندما تتلقى طريقة Update أو Remove عقدة من قائمة انتظار مختلفة؟ _

شكرا لملاحظات التغيير ؛)

طلبات صغيرة للتوضيح:

  • هل يمكننا تأهيل السؤال 4 بحيث ينطبق على العناصر غير الموجودة في قائمة الانتظار؟ (أي تلك التي تمت إزالتها)
  • هل يمكننا إضافة أسئلة وأجوبة حول الاستقرار ، أي الضمانات (إن وجدت) عند إلغاء ترتيب العناصر بنفس الأولوية (أفهم أنه لا توجد خطة لتقديم أي ضمانات ، من المهم معرفتها على سبيل المثال الجدولة).

pgolebiowski بخصوص طريقة Merge المقترحة:

public void Merge(PriorityQueue<TElement, TPriority> other); // O(1)

من الواضح أن مثل هذه العملية لن تحتوي على دلالات نسخ ، لذلك أتساءل عما إذا كان سيكون هناك أي مسكوكات حول إجراء تغييرات على this و other _after_ تم تنفيذ الدمج (على سبيل المثال فشل أي من الحالتين لتلبية خاصية الكومة).

تضمين التغريدة سيتم معالجة النقاط المثارة ، ETA 2020-10-04.

إذا كان لدى الآخرين أي ملاحظات / أسئلة / مخاوف / أفكار - يرجى المشاركة 😊

اقتراح أولوية قائمة الانتظار (الإصدار 2.2)

ملخص

يقترح مجتمع .NET Core أن يضيف إلى وظائف مكتبة النظام _priority queue_ ، وهي بنية بيانات يكون لكل عنصر فيها أولوية مرتبطة به. على وجه التحديد ، نقترح إضافة PriorityQueue<TElement, TPriority> إلى مساحة الاسم System.Collections.Generic .

العقيدة

في تصميمنا ، استرشدنا بالمبادئ التالية (ما لم تكن تعرف أفضل منها):

  • تغطية واسعة. نريد أن نقدم لعملاء .NET Core بنية بيانات قيّمة ومتعددة الاستخدامات بما يكفي لدعم مجموعة متنوعة من حالات الاستخدام.
  • تعلم من الأخطاء المعروفة. نحن نسعى جاهدين لتقديم وظيفة قائمة الانتظار ذات الأولوية التي ستكون خالية من المشكلات التي تواجه العملاء الموجودة في أطر العمل واللغات الأخرى ، مثل Java و Python و C ++ و Rust. سنتجنب اتخاذ خيارات تصميم يُعرف عنها أنها تجعل العملاء غير سعداء وتقليل فائدة قوائم الانتظار ذات الأولوية.
  • عناية فائقة مع قرارات الباب في اتجاه واحد. بمجرد تقديم API ، لا يمكن تعديلها أو حذفها ، بل توسيعها فقط. سنقوم بتحليل خيارات التصميم بعناية لتجنب الحلول دون المثلى التي سيظل عملاؤنا عالقين بها إلى الأبد.
  • تجنب شلل التصميم. نحن نقبل أنه قد لا يكون هناك حل مثالي. سنوازن بين المفاضلات والمضي قدمًا في التسليم ، لنوفر لعملائنا أخيرًا الوظائف التي كانوا ينتظرونها منذ سنوات.

خلفية

من وجهة نظر الزبون

من الناحية المفاهيمية ، فإن قائمة انتظار الأولوية هي مجموعة من العناصر ، حيث يكون لكل عنصر أولوية مرتبطة به. تتمثل الوظيفة الأكثر أهمية لقائمة الانتظار ذات الأولوية في أنها توفر وصولاً فعالاً إلى العنصر ذي الأولوية القصوى في المجموعة ، وخيارًا لإزالة هذا العنصر. قد يشمل السلوك المتوقع أيضًا: 1) القدرة على تعديل أولوية عنصر موجود بالفعل في المجموعة ؛ 2) القدرة على دمج قوائم انتظار متعددة ذات أولوية.

علوم الحاسوب الخلفية

قائمة انتظار الأولوية هي بنية بيانات مجردة ، أي أنها مفهوم له خصائص سلوكية معينة ، كما هو موضح في القسم السابق. تستند عمليات التنفيذ الأكثر كفاءة لقائمة انتظار الأولوية إلى أكوام. ومع ذلك ، على عكس المفهوم الخاطئ العام ، فإن الكومة هي أيضًا بنية بيانات مجردة ، ويمكن تحقيقها بطرق مختلفة ، كل منها يقدم مزايا وعيوب مختلفة.

معظم مهندسي البرمجيات على دراية فقط بتنفيذ الكومة الثنائية القائمة على المصفوفة - إنه أبسط واحد ، لكنه للأسف ليس الأكثر كفاءة. بالنسبة للإدخال العشوائي العام ، هناك مثالان لأنواع الكومة الأكثر كفاءة هما: الكومة الرباعية وكومة الاقتران . لمزيد من المعلومات حول الأكوام ، يرجى الرجوع إلى ويكيبيديا وهذه الورقة .

آلية التحديث هي التحدي الرئيسي في التصميم

أظهرت مناقشاتنا أن أكثر المجالات تحديًا في التصميم ، وفي نفس الوقت مع التأثير الأكبر على واجهة برمجة التطبيقات ، هي آلية التحديث. على وجه التحديد ، يتمثل التحدي في تحديد ما إذا كان المنتج الذي نرغب في تقديمه للعملاء يجب أن يدعم تحديث أولويات العناصر الموجودة بالفعل في المجموعة وكيفية ذلك.

هذه القدرة ضرورية لتنفيذ ، على سبيل المثال ، أقصر خوارزمية مسار Dijkstra أو جدولة الوظائف التي تحتاج إلى التعامل مع الأولويات المتغيرة. آلية التحديث مفقودة في Java ، والتي أظهرت أنها مخيبة للآمال للمهندسين ، على سبيل المثال في أسئلة StackOverflow الثلاثة التي تم عرضها أكثر من 32 ألف مرة: مثال ، مثال ، مثال . لتجنب تقديم API مع هذه القيمة المحدودة ، نعتقد أن أحد المتطلبات الأساسية لوظيفة قائمة الانتظار ذات الأولوية التي نقدمها سيكون دعم القدرة على تحديث الأولويات للعناصر الموجودة بالفعل في المجموعة.

لتقديم آلية التحديث ، يجب أن نتأكد من أن العميل يمكنه أن يكون محددًا بشأن ما يريد تحديثه بالضبط. لقد حددنا طريقتين لتقديم ذلك: أ) عبر مقابض. و ب) من خلال فرض تفرد العناصر في المجموعة. كل من هذه تأتي مع فوائد وتكاليف مختلفة.

الخيار (أ): مقابض. في هذا النهج ، في كل مرة يتم فيها إضافة عنصر إلى قائمة الانتظار ، توفر بنية البيانات مقبضها الفريد. إذا أراد العميل استخدام آلية التحديث ، فعليه تتبع هذه المقابض حتى يتمكن لاحقًا من تحديد العنصر الذي يرغب في تحديثه بشكل لا لبس فيه. التكلفة الأساسية لهذا الحل هي أن العملاء بحاجة إلى إدارة هذه المؤشرات. ومع ذلك ، هذا لا يعني أن هناك حاجة إلى أي تخصيصات داخلية لدعم المقابض ضمن قائمة انتظار الأولوية - أي كومة غير قائمة على المصفوفة تعتمد على العقد ، حيث تكون كل عقدة تلقائيًا مقبضها الخاص. كمثال ، راجع واجهة برمجة التطبيقات الخاصة بأسلوب PairingHeap.Update .

الخيار (ب): التفرد. يضع هذا النهج قيدين إضافيين على العميل: 1) يجب أن تتوافق العناصر الموجودة في قائمة انتظار الأولوية مع بعض دلالات المساواة ، مما يؤدي إلى ظهور فئة جديدة من أخطاء المستخدم المحتملة ؛ ب) لا يمكن تخزين عنصرين متساويين في نفس قائمة الانتظار. من خلال دفع هذه التكلفة ، نحصل على ميزة دعم آلية التحديث دون اللجوء إلى نهج المقبض. ومع ذلك ، فإن أي تنفيذ يعزز التفرد / المساواة لتحديد العنصر المراد تحديثه سيتطلب تعيينًا داخليًا إضافيًا ، بحيث يتم تنفيذه في O (1) وليس في O (n).

توصية

نوصي بإضافة فئة PriorityQueue<TElement, TPriority> إلى مكتبة النظام والتي تدعم آلية التحديث من خلال المقابض. سيكون التنفيذ الأساسي عبارة عن كومة إقران.

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

مثال على الاستخدام

1) العميل الذي لا يهتم بآلية التحديث

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) العميل الذي يهتم بآلية التحديث

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

التعليمات

1. بأي ترتيب تُعد قائمة انتظار الأولوية العناصر؟

بترتيب غير محدد ، بحيث يمكن أن يحدث التعداد في O (n) ، كما هو الحال مع HashSet . لن يوفر أي تطبيق فعال قدرات تعداد كومة في الوقت الخطي مع ضمان تعداد العناصر بالترتيب - وهذا يتطلب O (n log n). نظرًا لأن الطلب على مجموعة يمكن تحقيقه بشكل تافه باستخدام .OrderBy(x => x.Priority) ولا يهتم كل عميل بالتعداد باستخدام هذا الطلب ، فنحن نعتقد أنه من الأفضل تقديم أمر تعداد غير محدد.

2. لماذا لا توجد طريقة Contains أو TryGet ؟

توفير مثل هذه الأساليب له قيمة ضئيلة ، لأن العثور على عنصر في كومة يتطلب تعداد المجموعة بأكملها ، مما يعني أن أي طريقة Contains أو TryGet ستكون غلافًا حول التعداد. علاوة على ذلك ، للتحقق مما إذا كان العنصر موجودًا في المجموعة ، يجب أن تكون قائمة انتظار الأولوية على دراية بكيفية إجراء فحوصات المساواة للكائنات TElement ، وهو شيء لا نعتقد أنه يجب أن يقع ضمن مسؤولية قائمة انتظار الأولوية.

3. لماذا توجد حمولات زائدة Dequeue و TryDequeue تعيد PriorityQueueNode ؟

هذا مخصص للعملاء الذين يرغبون في استخدام طريقة Update أو Remove وتتبع المقابض. أثناء فصل عنصر من قائمة انتظار الأولوية ، سيحصلون على مقبض يمكنهم استخدامه لضبط حالة نظام تتبع المقبض.

4. ماذا يحدث عندما تتلقى الطريقة Update أو Remove عقدة من قائمة انتظار مختلفة؟

ستطرح قائمة انتظار الأولوية استثناءً. كل عقدة على علم بقائمة انتظار الأولوية التي تنتمي إليها ، وبالمثل فإن LinkedListNode<T> على علم بـ LinkedList<T> الذي تنتمي إليه. علاوة على ذلك ، إذا تمت إزالة عقدة من قائمة الانتظار ، فستؤدي محاولة استدعاء Update أو Remove عليها أيضًا إلى استثناء.

5. لماذا لا توجد طريقة Merge ؟

يمكن دمج قائمتين من قوائم الانتظار ذات الأولوية في وقت ثابت ، مما يجعلها ميزة مغرية لتقديمها للعملاء. ومع ذلك ، ليس لدينا بيانات لإثبات وجود طلب على هذه الوظيفة ، ولا يمكننا تبرير تضمينها في واجهة برمجة التطبيقات العامة. علاوة على ذلك ، فإن تصميم مثل هذه الوظيفة ليس تافهًا ، ونظرًا لأن هذه الميزة قد لا تكون ضرورية ، فقد يؤدي ذلك إلى تعقيد سطح واجهة برمجة التطبيقات وتنفيذها دون داع.

ومع ذلك ، فإن عدم تضمين طريقة Merge الآن هو باب ذو اتجاهين - إذا أبدى العملاء في المستقبل اهتمامًا بدعم وظيفة الدمج ، فسيكون من الممكن تمديد النوع PriorityQueue . على هذا النحو ، نوصي بعدم تضمين طريقة Merge حتى الآن ، والمضي قدمًا في الإطلاق.

6. هل توفر المجموعة ضمانًا للاستقرار؟

لن توفر المجموعة ضمانًا للاستقرار خارج الصندوق ، أي إذا تم وضع عنصرين في قائمة الانتظار بنفس الأولوية ، فلن يتمكن العميل من افتراض أنه سيتم فصلهما في ترتيب معين. ومع ذلك ، إذا كان العميل يرغب في تحقيق الاستقرار باستخدام PriorityQueue ، فيمكنه تحديد TPriority و IComparer<TPriority> المقابل الذي يضمن ذلك. علاوة على ذلك ، سيكون جمع البيانات حتميًا ، أي بالنسبة لتسلسل معين من العمليات ، سيتصرف دائمًا بنفس الطريقة ، مما يسمح بإمكانية التكاثر.

الملاحق

الملحق أ: لغات أخرى مع وظيفة قائمة الانتظار ذات الأولوية

| اللغة | اكتب | ملاحظات |
|: -: |: -: |: -: |
| جافا | طابور الأولوية | لتوسيع فئة الملخص AbstractQueue وتنفيذ الواجهة Queue . |
| الصدأ | BinaryHeap | |
| سويفت | CFBinaryHeap | |
| C ++ | Priority_queue | |
| بايثون | heapq | |
| اذهب | كومة | هناك واجهة كومة. |

الملحق ب: قابلية الاكتشاف

لاحظ أنه عند مناقشة هياكل البيانات ، يتم استخدام المصطلح _heap_ بمعدل 4 مرات أكثر من مصطلح _priority queue_.

  • "array" AND "data structure" - 17.400.000 نتيجة
  • "stack" AND "data structure" - 12.100.000 نتيجة
  • "queue" AND "data structure" - 3.850.000 نتيجة
  • "heap" AND "data structure" - نتائج 1.830.000
  • "priority queue" AND "data structure" - 430.000 نتيجة
  • "trie" AND "data structure" - 335.000 نتيجة

التغيير:

  • تمت إزالة الطريقة void Merge(PriorityQueue<TElement, TPriority> other) // O(1) .
  • تمت إضافة الأسئلة الشائعة رقم 5: لماذا لا توجد طريقة Merge ؟
  • الأسئلة الشائعة المعدلة رقم 4 للاحتفاظ أيضًا بالعقد التي تمت إزالتها من قائمة انتظار الأولوية.
  • تمت إضافة الأسئلة الشائعة رقم 6: هل توفر المجموعة ضمانًا للاستقرار؟

الأسئلة الشائعة الجديدة تبدو رائعة. لقد لعبت مع ترميز Dijkstra مقابل واجهة برمجة التطبيقات المقترحة ، مع قاموس المقابض ، وبدا الأمر جيدًا بشكل أساسي.

كان التعلم الصغير الوحيد الذي حصلت عليه من القيام بذلك هو أن المجموعة الحالية من أسماء الطرق / الأحمال الزائدة لا تعمل جيدًا للكتابة الضمنية لمتغيرات out . ما أردت فعله برمز C # هو TryDequeue(out var node) - لكن لسوء الحظ كنت بحاجة إلى إعطاء النوع الصريح لمتغير out كـ PriorityQueueNode<> وإلا فإن المترجم لم يعرف ما إذا كنت أراد عقدة طابور أو عنصرًا ذا أولوية.

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

سؤال التصميم الرئيسي الذي لم يتم حله من وجهة نظري هو ما إذا كان يجب أن يدعم التطبيق التحديثات ذات الأولوية أم لا. يمكنني رؤية ثلاثة مسارات ممكنة هنا:

  1. طلب التفرد / المساواة ودعم التحديثات بتمرير العنصر الأصلي.
  2. دعم التحديثات ذات الأولوية باستخدام المقابض.
  3. لا تدعم التحديثات ذات الأولوية.

هذه الأساليب متنافية ولها مجموعاتها الخاصة من المفاضلات ، على التوالي:

  1. النهج القائم على المساواة يعقد عقد API من خلال إجبار التفرد ويتطلب مسك دفاتر إضافي تحت الغطاء. يحدث هذا سواء كان المستخدم بحاجة إلى تحديثات ذات أولوية أم لا.
  2. قد يعني النهج القائم على المقبض تخصيصًا إضافيًا واحدًا على الأقل لكل عنصر مدرج في قائمة الانتظار. في حين أنه لا يفرض التفرد ، فإن انطباعي هو أنه بالنسبة للسيناريوهات التي تحتاج إلى تحديثات ، فإن هذا الثابت يكاد يكون ضمنيًا (على سبيل المثال ، لاحظ كيف يتعامل كلا المثالين المذكورين أعلاه في القواميس الخارجية المفهرسة بواسطة العناصر نفسها).
  3. التحديثات إما غير مدعومة على الإطلاق ، أو قد تتطلب اجتياز خطي للكومة. في حالة وجود عناصر مكررة ، يمكن أن تكون التحديثات غامضة.

تحديد تطبيقات PriorityQueue المشتركة

من المهم أن نحدد أيًا من الأساليب المذكورة أعلاه يمكن أن يوفر أفضل قيمة لمعظم مستخدمينا. لذلك كنت أتصفح قواعد بيانات .NET البرمجية ، الداخلية والعامة على حدٍ سواء ، من أجل مثيلات تطبيقات Heap / PriorityQueue من أجل فهم أفضل لأنماط الاستخدام الأكثر شيوعًا:

تقوم معظم التطبيقات بتطبيق اختلافات إما من نوع كومة الذاكرة المؤقتة أو جدولة المهام. تم استخدام حالات قليلة للخوارزميات مثل الفرز الطوبولوجي أو ترميز هوفمان. تم استخدام عدد أصغر لحساب المسافات في الرسوم البيانية. من بين 80 تطبيق PriorityQueue تم فحصه ، تم العثور على 9 فقط لتنفيذ بعض أشكال التحديثات ذات الأولوية.

في الوقت نفسه ، لا تدعم أي من تطبيقات Heap / PriorityQueue في مكتبات Python أو Java أو C ++ أو Go أو Swift أو Rust التحديثات ذات الأولوية في واجهات برمجة التطبيقات الخاصة بهم.

التوصيات

في ضوء هذه البيانات ، من الواضح بالنسبة لي أن. يحتاج إلى تطبيق PriorityQueue الأساسي الذي يعرض واجهة برمجة التطبيقات الأساسية (heapify / push / peek / pop) ، ويقدم ضمانات أداء معينة (على سبيل المثال ، لا توجد تخصيصات إضافية لكل قائمة) ولا يفرض قيود التفرد. هذا يعني أن التنفيذ _لا يدعم تحديثات الأولوية O (log n )_.

يجب أن نفكر أيضًا في المتابعة مع تنفيذ منفصل لكومة الذاكرة المؤقتة يدعم _O (سجل ن) _ التحديثات / الإزالة ، ويستخدم نهجًا قائمًا على المساواة. نظرًا لأن هذا سيكون نوعًا متخصصًا ، فلا ينبغي أن يكون مطلب التفرد مشكلة كبيرة.

لقد كنت أعمل على وضع نماذج أولية لكل من عمليات التنفيذ ، وسأتابع باقتراح API قريبًا.

شكرًا جزيلاً على التحليل ،eiriktsarpalis! إنني أقدر بشكل خاص الوقت اللازم لتحليل قاعدة بيانات Microsoft الداخلية للعثور على حالات الاستخدام ذات الصلة ودعم مناقشتنا بالبيانات.

قد يعني النهج القائم على المقبض تخصيصًا إضافيًا واحدًا على الأقل لكل عنصر مدرج في قائمة الانتظار.

هذا الافتراض خاطئ ، فأنت لست بحاجة إلى تخصيصات إضافية في أكوام قائمة على العقد. تعد كومة الإقران أكثر أداءً للإدخال العشوائي العام من الكومة الثنائية القائمة على الصفيف ، مما يعني أنه سيكون لديك حافز لاستخدام هذه الكومة القائمة على العقد حتى لقائمة انتظار ذات أولوية لا تدعم التحديثات. يمكنك أن ترى المعايير في الورقة التي أشرت إليها سابقًا .

من بين 80 تطبيق PriorityQueue تم فحصه ، تم العثور على 9 فقط لتنفيذ بعض أشكال التحديثات ذات الأولوية.

حتى مع الأخذ في الاعتبار هذه العينة الصغيرة ، فإن هذا يمثل 11-12٪ من جميع الاستخدامات. أيضًا ، قد يكون هذا تمثيلاً ناقصًا في مجالات معينة مثل ألعاب الفيديو ، حيث أتوقع أن تكون هذه النسبة أعلى.

في ضوء هذه البيانات ، يتضح لي أن [...]

لا أعتقد أن مثل هذا الاستنتاج واضح ، بالنظر إلى أن أحد الافتراضات الرئيسية خاطئ ويمكن الجدل حول ما إذا كان 11-12 ٪ من العملاء يمثلون حالة استخدام مهمة بدرجة كافية أم لا. ما أفتقده في تقييمك هو تقييم تأثير تكلفة "تحديثات دعم بنية البيانات" للعملاء الذين لا يهتمون بهذه الآلية - والتي بالنسبة لي ، هذه التكلفة لا تذكر ، حيث يمكنهم استخدام بنية البيانات دون أن تتأثر بآلية المقبض.

في الأساس:

| | 11-12٪ حالات استخدام | 88-89٪ حالات استخدام |
|: -: |: -: |: -: |
| يهتم بالتحديثات | نعم | لا |
| يتأثر سلبًا بالمقابض | غير متاح (هم مرغوب فيهم) | لا |
| يتأثر إيجابيا بالمقابض | نعم | لا |

بالنسبة لي ، هذا أمر لا يفكر فيه لصالح دعم حالات الاستخدام بنسبة 100٪ ، وليس فقط 88-89٪.

هذا الافتراض خاطئ ، فأنت لست بحاجة إلى تخصيصات إضافية في أكوام قائمة على العقد

إذا كان كل من الأولوية والعنصر من أنواع القيمة (أو إذا كان كلاهما من أنواع المراجع التي لا تملكها و / أو لا يمكنك تغيير النوع الأساسي) ، فهل يمكنك الارتباط بتطبيق يوضح عدم الحاجة إلى تخصيصات إضافية (أو فقط صف كيف يتم تحقيقها)؟ سيكون من المفيد أن نرى. شكرا.

سيكون من المفيد أن تشرح المزيد ، أو تقول فقط ما تحاول قوله. سأحتاج إلى توضيح ، ستكون هناك لعبة بينج بونج ، وسيتحول ذلك إلى مناقشة مطولة. بدلا من ذلك ، يمكننا ترتيب مكالمة.

أقول إننا نريد تجنب أي عملية Enqueue تتطلب تخصيصًا ، سواء من جانب المتصل أو جزء من التنفيذ (التخصيص الداخلي المحسّن جيد ، على سبيل المثال لتوسيع مصفوفة مستخدمة في التنفيذ). أحاول أن أفهم كيف يكون ذلك ممكنًا باستخدام كومة قائمة على العقدة (على سبيل المثال ، إذا تعرضت كائنات العقدة هذه للمتصل ، فإن ذلك يحظر التجميع بواسطة التطبيق بسبب مخاوف بشأن إعادة الاستخدام / التعرج غير المناسب). أريد أن أكون قادرًا على كتابة:
C# pq.Enqueue(42, 84);
ويكون ذلك لا تخصيص. كيف تحقق التطبيقات التي تشير إليها ذلك؟

أو قل ما تحاول قوله

ظننت أنني.

نريد أن نتجنب أي عملية Enqueue تتطلب تخصيصًا [...] أريد أن أكون قادرًا على كتابة: pq.Enqueue(42, 84); وجعل ذلك غير مخصص.

من أين تأتي هذه الرغبة؟ من الجيد أن يكون لديك تأثير جانبي للحل ، وليس مطلبًا يجب أن يرضيه 99.9٪ من العملاء. لا أفهم سبب اختيار هذا البعد منخفض التأثير لتحديد خيارات التصميم بين الحلول.

لا نقوم باختيارات تصميم بناءً على تحسينات لـ 0.1٪ من العملاء إذا أثرت سلبًا على 12٪ من العملاء في بُعد آخر. "الاهتمام بعدم التخصيصات" + "التعامل مع نوعين من القيم" هي حالة مميزة.

أجد بُعد السلوك / الوظيفة المدعومة أكثر أهمية ، خاصة عند تصميم بنية بيانات متعددة الاستخدامات للأغراض العامة لجمهور عريض ومجموعة متنوعة من حالات الاستخدام.

من أين تأتي هذه الرغبة؟

من الرغبة في أن تكون أنواع المجموعات الأساسية قابلة للاستخدام في السيناريوهات التي تهتم بالأداء. أنت تقول إن الحل المستند إلى العقدة سيدعم 100٪ من حالات الاستخدام: ليس هذا هو الحال إذا تم تخصيص كل قائمة انتظار ، تمامًا مثل List<T> ، Dictionary<TKey, TValue> ، HashSet<T> ، وهكذا يصبح on غير قابل للاستخدام في العديد من المواقف إذا تم تخصيصه لكل إضافة.

لماذا تعتقد أن "0.1٪" فقط هي التي تهتم بالتكاليف العامة لتخصيص هذه الأساليب؟ من أين تأتي تلك البيانات؟

"الاهتمام بعدم التخصيصات" + "التعامل مع نوعين من القيم" هي حالة مميزة

ليس. كما أنها ليست مجرد "نوعين من القيم". كما أفهمها ، سيتطلب الحل المقترح إما أ) تخصيصًا لكل قائمة انتظار بغض النظر عن Ts المعنية ، أو ب) سيتطلب اشتقاق نوع العنصر من نوع أساسي معروف والذي بدوره يحظر عددًا كبيرًا من الاستخدامات الممكنة لـ تجنب التخصيص الإضافي.

تضمين التغريدة
لذلك لا تنس أي خيارات ، أعتقد أن هناك خيارًا عمليًا 4 لإضافته إلى الخيارات 1 و 2 و 3 ، في قائمتك وهو حل وسط:

  1. تطبيق يدعم حالة الاستخدام بنسبة 12٪ ، بينما يُحسِّن أيضًا نسبة 88٪ الأخرى تقريبًا من خلال السماح بالتحديثات للعناصر المتساوية ، وبناء جدول البحث فقط _lazily_ المطلوب للقيام بهذه التحديثات في المرة الأولى التي يتم فيها استدعاء طريقة التحديث ( وتحديثه على التحديثات والإزالة اللاحقة). وبالتالي تكبد تكلفة أقل للتطبيقات التي لا تستخدم الوظيفة.

ربما لا نزال نقرر أنه نظرًا لوجود أداء إضافي متاح لـ 88٪ أو 12٪ من تطبيق لا يحتاج إلى بنية بيانات قابلة للتحديث ، أو مُحسَّن لواحد في المقام الأول ، فمن الأفضل توفير الخيارين 2 و 3 ، بدلاً من الخيار 4. لكنني اعتقدت أنه لا ينبغي أن ننسى وجود خيار آخر.

[أو أفترض أنه يمكنك فقط رؤية هذا كخيار أفضل 1 وتحديث وصف 1 ليقول إن مسك الدفاتر ليس قسريًا ، ولكنه كسول ، والسلوك المتوازن الصحيح مطلوب فقط عند استخدام التحديثات ...]

stephentoub هذا بالضبط ما كان

لماذا تعتقد أن 0.1٪ فقط يهتمون بالتكاليف العامة لتخصيص هذه الأساليب؟ من أين تأتي تلك البيانات؟

من الحدس ، أي المصدر نفسه الذي تعتقد أنه من الأهم بموجبه إعطاء الأولوية لـ "عدم وجود تخصيصات إضافية" على "القدرة على إجراء التحديثات". على الأقل بالنسبة لآلية التحديث ، لدينا البيانات التي يحتاجها 11-12٪ من العملاء لدعم هذا السلوك. لا أعتقد أن العملاء المقربين عن بُعد سيهتمون "بعدم وجود مخصصات إضافية".

في كلتا الحالتين ، أنت تختار ، لسبب ما ، التركيز على بُعد الذاكرة ، متجاهلاً الأبعاد الأخرى ، مثل السرعة الأولية ، وهي مقايضة أخرى لنهجك المفضل. سيكون التنفيذ المستند إلى المصفوفة الذي يوفر "عدم تخصيصات إضافية" أبطأ من التنفيذ المستند إلى العقدة. مرة أخرى ، أعتقد أنه من التعسفي هنا إعطاء الأولوية للذاكرة على السرعة.

لنعد خطوة إلى الوراء ونركز على ما يريده العملاء. لدينا خيار تصميم قد يجعل أو لا يجعل بنية البيانات غير قابلة للاستخدام بالنسبة لـ 12٪ من العملاء. أعتقد أننا سنحتاج إلى توخي الحذر الشديد في تقديم الأسباب التي تجعلنا نختار عدم دعمها.

سيكون التنفيذ المستند إلى المصفوفة الذي يوفر "عدم تخصيصات إضافية" أبطأ من التنفيذ المستند إلى العقدة.

يرجى مشاركة عمليتي تطبيق C # اللتين تستخدمهما لإجراء تلك المقارنة والمعايير المستخدمة للوصول إلى هذا الاستنتاج. الأوراق النظرية هي بالتأكيد قيمة لكنها ليست سوى قطعة صغيرة من اللغز. الشيء الأكثر أهمية هو عندما يلتقي المطاط بالطريق ، مع مراعاة تفاصيل النظام الأساسي المحدد والتطبيقات المحددة ، ويمكنك التحقق من صحة النظام الأساسي المحدد من خلال التنفيذ المحدد ومجموعات البيانات / أنماط الاستخدام النموذجية / المتوقعة. من المحتمل جدًا أن يكون تأكيدك صحيحًا. قد لا يكون كذلك. أرغب في رؤية عمليات التنفيذ / البيانات لفهمها بشكل أفضل.

يرجى مشاركة عمليتي تطبيق C # اللتين تستخدمهما لإجراء تلك المقارنة والمعايير المستخدمة للوصول إلى هذا الاستنتاج

هذه نقطة صحيحة ، فالورقة التي أقتبسها تقارن فقط وتعيير عمليات التنفيذ في C ++. تجري معايير متعددة مع مجموعات بيانات وأنماط استخدام مختلفة. أنا واثق تمامًا من أن هذا سيكون قابلاً للتحويل إلى C # ، ولكن إذا كنت تعتقد أن هذا شيء نحتاج إلى مضاعفته ، أعتقد أن أفضل مسار للعمل هو أن تطلب من زميل إجراء مثل هذه الدراسة.

pgolebiowski سأكون مهتمًا بفهم طبيعة اعتراضك بشكل أفضل. يدعو الاقتراح إلى نوعين منفصلين ، ألا يغطي ذلك متطلباتك؟

  1. تطبيق يدعم حالة الاستخدام بنسبة 12٪ ، بينما يُحسِّن أيضًا نسبة 88٪ الأخرى تقريبًا من خلال السماح بالتحديثات للعناصر المتساوية ، وبناء جدول البحث المطلوب لإجراء تلك التحديثات فقط في المرة الأولى التي يتم فيها استدعاء طريقة التحديث ( وتحديثه على التحديثات والإزالة اللاحقة). وبالتالي تكبد تكلفة أقل للتطبيقات التي لا تستخدم الوظيفة.

من المحتمل أن أصنف ذلك على أنه تحسين أداء للخيار 1 ، ولكني أرى مشكلتين في هذا النهج المعين:

  • أصبح التحديث الآن _O (n) _ ، مما قد يؤدي إلى أداء غير متوقع اعتمادًا على أنماط الاستخدام.
  • جدول البحث مطلوب أيضًا للتحقق من صحة التفرد. سيتم قبول إدراج نفس العنصر مرتين قبل استدعاء التحديث ويمكن القول إنه يؤدي إلى وضع قائمة الانتظار في حالة غير متسقة.

eiriktsarpalis لها فقط O (n) مرة واحدة ، و O (1) بعد ذلك ، وهي O (1) مستهلكة. ويمكنك تأجيل التحقق من صحة التفرد حتى التحديث الأول. لكن ربما هذا ذكي للغاية. فصلين أسهل في الشرح.

لقد أمضيت الأيام القليلة الماضية في وضع نماذج أولية لتطبيقي PriorityQueue: تنفيذ أساسي بدون دعم التحديث وتنفيذ يدعم التحديثات باستخدام تكافؤ العناصر. لقد قمت بتسمية السابق PriorityQueue والأخير لعدم وجود اسم أفضل ، PrioritySet . هدفي هو قياس بيئة عمل API ومقارنة الأداء.

يمكن العثور على التطبيقات في هذا الريبو . يتم تنفيذ كلا الفئتين باستخدام أكوام رباعية تعتمد على الصفيف. يستخدم التنفيذ القابل للتحديث أيضًا قاموسًا يقوم بتعيين العناصر إلى فهارس الكومة الداخلية.

الأولوية الأساسية

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

إليك عينة أساسية باستخدام النوع

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

الأولوية القابلة للتحديث

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

مقارنة الأداء

لقد كتبت معيارًا بسيطًا للفرز المتراكم يقارن بين التطبيقين في أبسط تطبيق لهما. لقد قمت أيضًا بتضمين معيار فرز يستخدم Linq للمقارنة:

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

| الطريقة | الحجم | يعني | خطأ | StdDev | النسبة | نسبة SD | الجيل 0 | الجيل 1 | الجيل 2 | المخصصة |
| -------------- | ------ | -------------: | -----------: | -----------: | ------: | --------: | --------: | -------- : | --------: | ----------: |
| LinqSort | 30 | 1.439 لنا | 0.0072 لنا | 0.0064 لنا | 1.00 | 0.00 | 0.0095 | - | - | 672 ب |
| الأولوية 30 | 1.450 لنا | 0.0085 لنا | 0.0079 لنا | 1.01 | 0.01 | - | - | - | - |
| مجموعة الأولوية | 30 | 2.778 لنا | 0.0217 لنا | 0.0192 لنا | 1.93 | 0.02 | - | - | - | - |
| | | | | | | | | | | |
| LinqSort | 300 | 24.727 لنا | 0.1032 لنا | 0.0915 لنا | 1.00 | 0.00 | 0.0305 | - | - | 3912 ب |
| الأولوية 300 | 29.510 لنا | 0.0995 لنا | 0.0882 لنا | 1.19 | 0.01 | - | - | - | - |
| مجموعة الأولوية | 300 | 47.715 لنا | 0.4455 لنا | 0.4168 لنا | 1.93 | 0.02 | - | - | - | - |
| | | | | | | | | | | |
| LinqSort | 3000 | 412.015 لنا | 1.5495 لنا | 1.3736 لنا | 1.00 | 0.00 | 0.4883 | - | - | 36312 ب |
| الأولوية 3000 | 491.722 لنا | 4.1463 لنا | 3.8785 لنا | 1.19 | 0.01 | - | - | - | - |
| مجموعة الأولوية | 3000 | 677.959 لنا | 3.1996 لنا | 2.4981 لنا | 1.64 | 0.01 | - | - | - | - |
| | | | | | | | | | | |
| LinqSort | 30000 | 5،223.560 لنا | 11.9077 لنا | 9.9434 لنا | 1.00 | 0.00 | 93.7500 | 93.7500 | 93.7500 | 360910 ب |
| الأولوية 30000 | 5،688.625 لنا | 53.0746 لنا | 49.6460 لنا | 1.09 | 0.01 | - | - | - | 2 ب |
| مجموعة الأولوية | 30000 | 8124.306 لنا | 39.9498 لنا | 37.3691 لنا | 1.55 | 0.01 | - | - | - | 4 ب |

كما هو متوقع ، تضيف النفقات العامة لمواقع عناصر التعقب نجاحًا كبيرًا في الأداء ، تقريبًا بنسبة 40-50٪ أبطأ مقارنةً بتنفيذ خط الأساس.

أنا أقدر كل هذا الجهد ، وأرى أنه استغرق الكثير من الوقت والطاقة.

  1. لا أرى حقًا سبب وجود بُنيتي بيانات متطابقتين تقريبًا ، حيث يكون أحدهما نسخة أدنى من الآخر.
  2. علاوة على ذلك ، حتى إذا كنت ترغب في الحصول على هذين الإصدارين من قائمة انتظار الأولوية ، لا أرى كيف يكون الإصدار "المتفوق" أفضل من اقتراح قائمة انتظار الأولوية ( الإصدار

TL ؛ د:

  • هذا الاقتراح مثير !! لكنها لا تناسب حالات الاستخدام عالية الأداء.
  • > 90٪ من حمل أداء الهندسة الحسابية / تخطيط الحركة هو PQ enqueue / dequeue لأن هذا هو N ^ m LogN السائد في الخوارزمية.
  • أنا أؤيد تطبيقات PQ المنفصلة. عادةً لا أحتاج إلى تحديثات ذات أولوية ، والأداء السيئ مرتين أمر غير مقبول.
  • يعتبر PrioritySet اسمًا محيرًا ولا يمكن اكتشافه مقابل PriorityQueue
  • يبدو تخزين الأولوية مرتين (مرة واحدة في العنصر ومرة ​​في قائمة الانتظار) مكلفًا. نسخ الهيكل واستخدام الذاكرة.
  • إذا كانت أولوية الحوسبة باهظة الثمن ، فسأقوم ببساطة بتخزين tuple (priority: ComputePriority(element), element) في PQ ، وستكون وظيفة الحصول على الأولوية ببساطة هي tuple => tuple.priority .
  • يجب قياس الأداء لكل عملية أو بدلاً من ذلك في حالات الاستخدام في العالم الحقيقي (على سبيل المثال ، بحث متعدد النهايات متعدد البداية محسّن على رسم بياني)
  • سلوك التعداد غير المنظم غير متوقع. هل تفضل Dequeue () التي تشبه قائمة الانتظار - ترتيب الدلالات؟
  • ضع في اعتبارك دعم عملية النسخ وعملية الدمج.
  • يجب أن تكون العمليات الأساسية 0-تخصيص في حالة استخدام ثابتة. سأجمع هذه الطوابير.
  • ضع في اعتبارك دعم EnqueueMany الذي يؤدي heapify للمساعدة في التجميع.

أعمل في البحث عالي الأداء (تخطيط الحركة) ورمز الهندسة الحسابية (مثل خوارزميات خط المسح) ذات الصلة بكل من الروبوتات والألعاب ، وأستخدم الكثير من قوائم الانتظار المخصصة ذات الأولوية والملفوفة يدويًا. حالة الاستخدام الشائعة الأخرى التي أملكها هي استعلام Top-K حيث لا تكون الأولوية القابلة للتحديث مفيدة.

بعض التعليقات المتعلقة بمناقشة التطبيقين (نعم مقابل لا دعم التحديث).

تسمية:

  • يتضمن PrioritySet تعيين دلالات ، لكن قائمة الانتظار لا تطبق ISet.
  • أنت تستخدم اسم UpdatablePriorityQueue الذي يسهل اكتشافه إذا كنت أبحث في PriorityQueue.

أداء:

  • دائمًا ما يكون أداء قائمة انتظار الأولوية هو عنق الزجاجة في أدائي (> 90٪) في خوارزميات الهندسة / التخطيط الخاصة بي
  • ضع في اعتبارك تمرير Funcأو المقارنةإلى ctor بدلاً من نسخ الأولوية حولها (مكلف!). إذا كانت أولوية الحوسبة باهظة الثمن ، فسوف أقوم بإدراج (الأولوية ، العنصر) في PQ وتمرير المقارنة التي تنظر إلى الأولوية المخزنة مؤقتًا الخاصة بي.
  • لا يحتاج عدد كبير من الخوارزميات الخاصة بي إلى تحديثات PQ. كنت أفكر في استخدام PQ مدمج لا يدعم التحديثات ، ولكن إذا كان هناك شيء له تكلفة أداء مضاعفة لدعم ميزة لا أحتاجها (التحديث) ، فهذا غير مفيد بالنسبة لي.
  • لتحليل الأداء / المقايضات ، سيكون من المهم معرفة التكلفة النسبية لوقت الجدار لقائمة الانتظار / eiriktsarpalis - "التتبع أبطأ
  • كان من دواعي سروري أن أرى صانعيك يؤدون Heapify. ضع في اعتبارك مُنشئًا يأخذ IList أو List أو Array لتجنب تخصيصات التعداد.
  • ضع في اعتبارك تعريض EnqueueMany الذي يؤدي Heapify إذا كان PQ فارغًا في البداية ، لأنه في الأداء العالي يكون من الشائع تجميع المجموعات.
  • ضع في اعتبارك جعل صفيف محو وليس صفريًا إذا كانت العناصر لا تحتوي على مراجع.
  • التخصيصات في قائمة الانتظار / ديو غير مقبولة. خوارزمياتي غير مخصصة للأداء لأسباب تتعلق بالأداء ، مع مجموعات ترابط محلية مجمعة.

واجهات برمجة التطبيقات:

  • يعد استنساخ قائمة انتظار ذات أولوية عملية تافهة مع عمليات التنفيذ الخاصة بك وهو مفيد في كثير من الأحيان.

    • ذات صلة: هل يجب أن يحتوي تعداد قائمة انتظار الأولوية على دلالات تشبه قائمة الانتظار؟ أتوقع مجموعة ترتيب dequeue ، على غرار ما يفعله Queue. أتوقع أن القائمة الجديدة (myPriorityQueue) لن تغير أولوية قائمة الانتظار ، بل أن تعمل كما وصفت للتو.

  • كما ذكرنا أعلاه ، من الأفضل أن تأخذ Func<TElement, TPriority> بدلاً من الإدراج مع الأولوية. إذا كانت أولوية الحوسبة باهظة الثمن ، يمكنني ببساطة إدخال (priority, element) وتقديم func tuple => tuple.priority
  • أحيانًا يكون دمج قائمتين من قوائم الانتظار ذات الأولوية مفيدًا.
  • من الغريب أن تقوم Peek بإرجاع TItem لكن Enumeration & Enqueue لها (TItem ، TP الأولوية).

ومع ذلك ، بالنسبة لعدد كبير من الخوارزميات الخاصة بي ، تحتوي عناصر قائمة انتظار الأولوية الخاصة بي على أولوياتها ، وتخزين ذلك مرتين (مرة واحدة في PQ ، ومرة ​​في العناصر) يبدو غير فعال. هذا هو الحال بشكل خاص إذا كنت أطلب من خلال العديد من المفاتيح (حالة استخدام مماثلة لـ OrderBy ، ثم By.ThenBy). تقوم واجهة برمجة التطبيقات هذه أيضًا بتنظيف الكثير من التناقضات حيث يأخذ الإدراج أولوية ولكن Peek لا يعيدها.

أخيرًا ، تجدر الإشارة إلى أنني غالبًا ما أقوم بإدراج فهارس مصفوفة في قائمة انتظار الأولوية ، بدلاً من عناصر المصفوفة نفسها . هذا مدعوم من قبل جميع واجهات برمجة التطبيقات التي تمت مناقشتها حتى الآن. على سبيل المثال ، إذا كنت أقوم بمعالجة بداية / نهايات الفواصل الزمنية على خط محور س ، فقد يكون لدي أحداث قائمة انتظار ذات أولوية (x, isStartElseEnd, intervalId) وأقوم بالترتيب حسب x ثم بواسطة isStartElseEnd. هذا غالبًا لأن لدي هياكل بيانات أخرى يتم تعيينها من فهرس إلى بعض البيانات المحسوبة.

pgolebiowski لقد تنفيذ كومة الاقتران المقترح للمعايير ، فقط حتى نتمكن من مقارنة مثيلات جميع الأساليب الثلاثة مباشرة. ها هي النتائج:

| الطريقة | الحجم | يعني | خطأ | StdDev | الوسيط | الجيل 0 | الجيل 1 | الجيل 2 | المخصصة |
| -------------- | -------- | -------------------: | ---- -------------: | -----------------: | ---------------- ---: | ----------: | ------: | ------: | -----------: |
| الأولوية 10 | 774.7 نانوثانية | 3.30 نانوثانية | 3.08 نانوثانية | 773.2 نانوثانية | - | - | - | - |
| مجموعة الأولوية | 10 | 1،643.0 نانوثانية | 3.89 نانوثانية | 3.45 نانوثانية | 1،642.8 نانوثانية | - | - | - | - |
| الاقتران | 10 | 1660.2 نانوثانية | 14.11 نانوثانية | 12.51 نانوثانية | 1،657.2 نانوثانية | 0.0134 | - | - | 960 ب |
| الأولوية 50 | 6413.0 نانوثانية | 14.95 نانوثانية | 13.99 نانوثانية | 6409.5 نانوثانية | - | - | - | - |
| مجموعة الأولوية | 50 | 12193.1 نانوثانية | 35.41 نانوثانية | 29.57 نانوثانية | 12188.3 نانوثانية | - | - | - | - |
| الاقتران | 50 | 13955.8 نانوثانية | 193.36 نانوثانية | 180.87 نانوثانية | 13989.2 نانوثانية | 0.0610 | - | - | 4800 ب |
| الأولوية 150 | 27402.5 نانوثانية | 76.52 نانوثانية | 71.58 نانوثانية | 27410.2 نانوثانية | - | - | - | - |
| مجموعة الأولوية | 150 | 48485.8 نانوثانية | 160.22 نانوثانية | 149.87 نانوثانية | 48476.3 نانوثانية | - | - | - | - |
| الاقتران | 150 | 56951.2 نانوثانية | 190.52 نانوثانية | 168.89 نانوثانية | 56953.6 نانوثانية | 0.1831 | - | - | 14400 ب |
| الأولوية 500 | 124،933.7 نانوثانية | 429.20 نانوثانية | 380.48 نانوثانية | 124،824.4 نانوثانية | - | - | - | - |
| مجموعة الأولوية | 500 | 206.310.0 نانوثانية | 433.97 نانوثانية | 338.81 نانوثانية | 206.319.0 نانوثانية | - | - | - | - |
| الاقتران | 500 | 229،423.9 نانوثانية | 3213.33 نانوثانية | 2،848.53 نانوثانية | 230398.7 نانوثانية | 0.4883 | - | - | 48000 ب |
| الأولوية 1000 | 284481.8 نانوثانية | 475.91 نانوثانية | 445.16 نانوثانية | 284،445.6 نانوثانية | - | - | - | - |
| مجموعة الأولوية | 1000 | 454989.4 نانوثانية | 3712.11 نانوثانية | 3،472.31 نانوثانية | 455354.0 نانوثانية | - | - | - | - |
| الاقتران | 1000 | 459،049.3 نانوثانية | 1،706.28 نانوثانية | 1424.82 نانوثانية | 459364.9 نانوثانية | 0.9766 | - | - | 96000 ب |
| الأولوية 10000 | 3،788،802.4 نانو ثانية | 11،715.81 نانوثانية | 10958.98 نانوثانية | 3،787،811.9 نانوثانية | - | - | - | 1 ب |
| مجموعة الأولوية | 10000 | 5،963،100.4 نانو ثانية | 26669.04 نانوثانية | 22269.86 نانوثانية | 5950915.5 نانوثانية | - | - | - | 2 ب |
| الاقتران | 10000 | 6،789،719.0 نانوثانية | 134453.01 نانوثانية | 265397.13 نانوثانية | 6،918،392.9 نانوثانية | 7.8125 | - | - | 960002 ب |
| الأولوية 1000000 | 595،059،170.7 نانوثانية | 4،001،349.38 نانوثانية | 3،547،092.00 نانوثانية | 595716610.5 نانوثانية | - | - | - | 4376 ب |
| مجموعة الأولوية | 1000000 | 1،592،037،780.9 نانوثانية | 13،925،896.05 نانوثانية | 12،344،944.12 نانوثانية | 1،591،051،886.5 نانوثانية | - | - | - | 288 ب |
| الاقتران | 1000000 | 1،858،670،560.7 نانوثانية | 36405433.20 نانوثانية | 59815170.76 نانوثانية | 1،838،721،629.0 نانوثانية | 1000.0000 | - | - | 96000376 ب |

الماخذ الرئيسية

  • ~ أداء كومة الاقتران بشكل مقارب أفضل بكثير من نظيراتها المدعومة من المصفوفة. ومع ذلك ، يمكن أن يكون أبطأ بمقدار 2x بالنسبة لأحجام الكومة الصغيرة (<50 عنصرًا) ، ويلحق ما يقرب من 1000 عنصر ، ولكنه أسرع بما يصل إلى 2x لأكوام الحجم 10 ^ 6 ~.
  • كما هو متوقع ، ينتج عن كومة الازدواج قدرًا كبيرًا من عمليات تخصيص الكومة.
  • تنفيذ "PrioritySet" بطيء باستمرار ~ الأبطأ من بين جميع المتنافسين الثلاثة ~ ، لذلك قد لا نرغب في اتباع هذا النهج بعد كل شيء.

~ في ضوء ما سبق ، ما زلت أعتقد أن هناك مقايضات صحيحة بين كومة المصفوفة الأساسية ونهج كومة الاقتران ~.

تحرير: تم تحديث النتائج بعد إصلاح الخلل في معاييري ، شكرًاVisualMelon

eiriktsarpalis إن معيارك لـ PairingHeap هو ، على ما أعتقد ، خطأ: المعلمات إلى Add هي الطريقة الخاطئة. عندما تقوم بتبديلها ، تكون قصة مختلفة: https://gist.github.com/VisualMelon/00885fe50f7ab0f4ae5cd1307312109f

(لقد فعلت الشيء نفسه بالضبط عندما طبقته لأول مرة)

لاحظ أن هذا لا يعني أن كومة الاقتران أسرع أو أبطأ ، بل يبدو أنها تعتمد بشكل كبير على توزيع / ترتيب البيانات المقدمة.

eiriktsarpalis re: فائدة مجموعة PrioritySet ...
لا ينبغي أن نتوقع أن يكون التحديث أي شيء آخر غير أبطأ لفرز الكومة ، لأنه لا يحتوي على تحديثات ذات أولوية في السيناريو. (أيضًا بالنسبة إلى heapsort ، من المحتمل أنك تريد الاحتفاظ بالنسخ المكررة ، فالمجموعة ليست مناسبة.)

يجب أن يكون اختبار عباد الشمس لمعرفة ما إذا كانت مجموعة PrioritySet مفيدة هي خوارزميات قياس الأداء التي تستخدم التحديثات ذات الأولوية ، مقابل التنفيذ غير المحدث للخوارزمية نفسها ، وإدراج القيم المكررة وتجاهل التكرارات عند إلغاء ترتيب الصف.

شكرًا VisualMelon ، لقد قمت بتحديث نتائجي وتعليقاتي بعد الإصلاح المقترح.

بل يبدو أنه يعتمد بشكل كبير على توزيع / ترتيب البيانات المقدمة.

أعتقد أنه ربما استفاد من حقيقة أن الأولويات المذكورة كانت رتيبة.

يجب أن يكون اختبار عباد الشمس لمعرفة ما إذا كانت مجموعة PrioritySet مفيدة هي خوارزميات قياس الأداء التي تستخدم التحديثات ذات الأولوية ، مقابل التنفيذ غير المحدث للخوارزمية نفسها ، وإدراج القيم المكررة وتجاهل التكرارات عند إلغاء ترتيب الصف.

@ TimLovellSmith كان هدفي هنا هو قياس الأداء لتطبيق PriorityQueue الأكثر شيوعًا: بدلاً من قياس أداء التحديثات ، أردت أن أرى تأثير الحالة التي لا تكون فيها هناك حاجة إلى التحديثات على الإطلاق. ومع ذلك ، قد يكون من المنطقي إنتاج معيار منفصل يقارن كومة الإقران بتحديثات "PrioritySet".

miyu شكرا لملاحظاتك التفصيلية ، إنه محل تقدير كبير جدا!

TimLovellSmith @ كتبت معيارًا بسيطًا يستخدم التحديثات:

| الطريقة | الحجم | يعني | خطأ | StdDev | الوسيط | الجيل 0 | الجيل 1 | الجيل 2 | المخصصة |
| ------------ | -------- | ---------------: | ---------- -----: | ---------------: | ---------------: | -------: | ------: | ------: | -----------: |
| مجموعة الأولوية | 10 | 1.052 لنا | 0.0106 لنا | 0.0099 لنا | 1.055 لنا | - | - | - | - |
| الاقتران | 10 | 1.055 لنا | 0.0042 لنا | 0.0035 لنا | 1.055 لنا | 0.0057 | - | - | 480 ب |
| مجموعة الأولوية | 50 | 7.394 لنا | 0.0527 لنا | 0.0493 لنا | 7.380 لنا | - | - | - | - |
| الاقتران | 50 | 8.587 لنا | 0.1678 لنا | 0.1570 لنا | 8.634 لنا | 0.0305 | - | - | 2400 ب |
| مجموعة الأولوية | 150 | 27.522 لنا | 0.0459 لنا | 0.0359 لنا | 27.523 لنا | - | - | - | - |
| الاقتران | 150 | 32.045 لنا | 0.1076 لنا | 0.1007 لنا | 32.019 لنا | 0.0610 | - | - | 7200 ب |
| مجموعة الأولوية | 500 | 109.097 لنا | 0.6548 لنا | 0.6125 لنا | 109.162 لنا | - | - | - | - |
| الاقتران | 500 | 131.647 لنا | 0.5401 لنا | 0.4510 لنا | 131.588 لنا | 0.2441 | - | - | 24000 ب |
| مجموعة الأولوية | 1000 | 238.184 لنا | 1.0282 لنا | 0.9618 لنا | 238.457 لنا | - | - | - | - |
| الاقتران | 1000 | 293.236 لنا | 0.9396 لنا | 0.8789 لنا | 293.257 لنا | 0.4883 | - | - | 48000 ب |
| مجموعة الأولوية | 10000 | 3035.982 لنا | 12.2952 لنا | 10.8994 لنا | 3،036.985 لنا | - | - | - | 1 ب |
| الاقتران | 10000 | 3،388.685 لنا | 16.0675 لنا | 38.1861 لنا | 3374.565 لنا | - | - | - | 480002 ب |
| مجموعة الأولوية | 1000000 | 841406.888 لنا | 16788.4775 لنا | 15703.9522 لنا | 840،888.389 لنا | - | - | - | 288 ب |
| الاقتران | 1000000 | 989،966.501 لنا | 19722.6687 لنا | 30705.8191 لنا | 996075.410 لنا | - | - | - | 48000448 ب |

في ملاحظة منفصلة ، هل كانت مناقشاتهم / تعليقاتهم حول عدم الاستقرار مشكلة (أو ليست مشكلة) لاستخدامات الأشخاص؟

هل كانت مناقشاتهم / تعليقاتهم حول عدم الاستقرار مشكلة (أو ليست مشكلة) بالنسبة لاستخدام الناس

لا يضمن أي من التطبيقات الاستقرار ، ولكن يجب أن يكون من السهل جدًا للمستخدمين الحصول على الاستقرار من خلال زيادة الترتيب الترتيبي بترتيب الإدخال:

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

لتلخيص بعض مشاركاتي السابقة ، كنت أحاول تحديد الشكل الذي ستبدو عليه قائمة انتظار أولوية .NET الشائعة ، لذلك قمت بالاطلاع على البيانات التالية:

  • أنماط استخدام قائمة انتظار الأولوية الشائعة في التعليمات البرمجية المصدر لـ .NET.
  • عمليات تنفيذ PriorityQueue في المكتبات الأساسية للأطر المتنافسة.
  • معايير مختلف نماذج قائمة انتظار الأولوية .NET.

والتي أسفرت عن الوجبات السريعة التالية:

  • 90٪ من حالات استخدام قائمة الانتظار ذات الأولوية لا تتطلب تحديثات ذات أولوية.
  • ينتج عن دعم التحديثات ذات الأولوية عقد واجهة برمجة تطبيقات أكثر تعقيدًا (يتطلب مقابض أو تفرد عنصر).
  • في معاييري ، تكون عمليات التنفيذ التي تدعم التحديثات ذات الأولوية أبطأ بمقدار 2-3x مقارنة بالتطبيقات التي لا تدعم ذلك.

الخطوات التالية

من الآن فصاعدًا ، أقترح اتخاذ الإجراءات التالية لـ .NET 6:

  1. قدم فئة System.Collections.Generic.PriorityQueue بسيطة ، وتلبي معظم متطلبات مستخدمينا وتكون فعالة قدر الإمكان. سيستخدم كومة رباعية مدعومة بمصفوفة ولن يدعم التحديثات ذات الأولوية. يمكن العثور على نموذج أولي للتنفيذ هنا . سوف أقوم بإنشاء مشكلة منفصلة توضح بالتفصيل اقتراح واجهة برمجة التطبيقات قريبًا.

  2. نحن ندرك الحاجة إلى أكوام تدعم التحديثات الفعالة ذات الأولوية ، لذلك سنواصل العمل من أجل تقديم فئة متخصصة تلبي هذا المطلب. نقوم بتقييم اثنين من النماذج الأولية [ 1 ، 2 ] لكل منها مجموعاتها الخاصة من المفاضلات. توصيتي بتقديم هذا النوع في مرحلة لاحقة ، حيث أن هناك حاجة إلى مزيد من العمل لإنهاء التصميم.

في هذه اللحظة ، أود أن أشكر المساهمين في هذا الموضوع ، ولا سيما pgolebiowski و @ TimLovellSmith. لعبت ملاحظاتك دورًا كبيرًا في توجيه عملية التصميم لدينا. آمل أن أستمر في تلقي إدخالاتك أثناء قيامنا بإخراج تصميم قائمة انتظار الأولوية القابلة للتحديث.

من الآن فصاعدًا ، أقترح اتخاذ الإجراءات التالية لـ .NET 6: [...]

يبدو جيدا :)

قدم فئة System.Collections.Generic.PriorityQueue بسيطة ، وتلبي معظم متطلبات مستخدمينا وتكون فعالة قدر الإمكان. سيستخدم كومة رباعية مدعومة بمصفوفة ولن يدعم التحديثات ذات الأولوية.

إذا كان لدينا قرار من قبل مالكي قاعدة البيانات بأن هذا الاتجاه قد تمت الموافقة عليه ومرغوب فيه ، فهل يمكنني الاستمرار في قيادة تصميم واجهة برمجة التطبيقات لتلك القطعة وتوفير التنفيذ النهائي؟

في هذه اللحظة ، أود أن أشكر المساهمين في هذا الموضوع ، ولا سيما pgolebiowski و @ TimLovellSmith. لعبت ملاحظاتك دورًا كبيرًا في توجيه عملية التصميم لدينا. آمل أن أستمر في تلقي إدخالاتك أثناء قيامنا بإخراج تصميم قائمة انتظار الأولوية القابلة للتحديث.

لقد كانت رحلة رائعة: د

تمت الموافقة للتو على واجهة برمجة التطبيقات لـ System.Collections.Generic.PriorityQueue<TElement, TPriority> . لقد قمت بإنشاء مشكلة منفصلة لمواصلة محادثتنا حول تنفيذ كومة محتمل يدعم التحديثات ذات الأولوية.

سأغلق هذا العدد ، شكرًا لكم جميعًا على مساهماتكم!

ربما يمكن لشخص ما أن يكتب في هذه الرحلة! 6 سنوات كاملة لواحد API. :) أي فرصة للفوز بموسوعة غينيس؟

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات