corefxlabリポジトリの最新の提案を参照してください。
https://github.com/dotnet/corefx/issues/574#issuecomment-307971397からの提案
優先キューの要素は一意です。 そうでない場合は、アイテムの「ハンドル」を導入して、更新/削除を有効にする必要があります。 または、更新/削除のセマンティクスを最初/すべてに適用する必要がありますが、これは奇妙なことです。
`` `c#
パブリッククラスPriorityQueue
:IEnumerable、
IEnumerable <(TElement要素、TPriority優先度)>、
IReadOnlyCollection <(TElement要素、TPriority優先度)>
// ICollectionは意図的に含まれていません
{{
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
public Func<TElement, TPriority> PrioritySelector { get; }
public void Enqueue(TElement element);
public void Update(TElement element);
}
「」
未解決の質問:
PriorityQueue
とHeap
IHeap
とコンストラクターのオーバーロードを導入しますか? (後で待つ必要がありますか?)IPriorityQueue
紹介しますか? (後で待つ必要があります- IDictionary
例)(TElement element, TPriority priority)
とKeyValuePair<TPriority, TElement>
Peek
とDequeue
は、タプルではなくout
引数を指定する必要がありますか?Peek
とDequeue
スローはまったく役に立ちますか?問題https://github.com/dotnet/corefx/issues/163は、コア.NETコレクションデータ構造への優先度キューの追加を要求しました。
この投稿は重複していますが、corefxAPIレビュープロセスへの正式な提出を目的としています。 問題の内容は、新しいSystem.Collections.Generic.PriorityQueueの_speclet_です。
承認されれば、私はPRに貢献します。
.NET基本クラスライブラリ(BCL)は現在、順序付けられた生産者/消費者コレクションのサポートを欠いています。 多くのソフトウェアアプリケーションに共通する要件は、時間の経過とともにアイテムのリストを生成し、受け取った順序とは異なる順序でアイテムを処理する機能です。
名前空間のSystem.Collections階層内には、並べ替えられたアイテムのコレクションをサポートする3つの一般的なデータ構造があります。 System.Collections.Generic.SortedList、System.Collections.Generic.SortedSet、およびSystem.Collections.Generic.SortedDictionary。
これらのうち、SortedSetとSortedDictionaryは、重複する値を生成する生産者/消費者パターンには適していません。 SortedListの複雑さは、AddとRemoveの両方でΘ(n)最悪の場合です。
生産者/消費者の使用パターンを持つ順序付けられたコレクションのメモリと時間効率の高いデータ構造は、優先度付きキューです。 容量のサイズ変更が必要な場合を除いて、最悪の場合の挿入(エンキュー)および最上位の削除(デキュー)のパフォーマンスはΘ(logn)であり、BCLに存在する既存のオプションよりもはるかに優れています。
優先キューは、さまざまなクラスのアプリケーションに幅広く適用できます。 優先度付きキューのウィキペディアのページには、よく理解されているさまざまなユースケースのリストがあります。 高度に専門化された実装では、カスタムの優先度キューの実装が必要になる場合がありますが、標準の実装では、幅広い使用シナリオがカバーされます。 優先キューは、複数のプロデューサーの出力をスケジュールする場合に特に役立ちます。これは、高度に並列化されたソフトウェアの重要なパターンです。
C ++標準ライブラリとJavaの両方が、基本APIの一部として優先キュー機能を提供していることは注目に値します。
`` `C#
名前空間System.Collections.Generic
{{
///
///ソートされた順序で削除されるオブジェクトのコレクションを表します。
///
///
[DebuggerDisplay( "Count = {count}")]
[DebuggerTypeProxy(typeof(System_PriorityQueueDebugView <>))]
パブリッククラスPriorityQueue
{{
///
///の新しいインスタンスを初期化します
///デフォルトの比較子を使用します。
///
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を使用して構築する| Θ(n)| |
| エンキュー| Θ(logn)| |
| デキュー| Θ(logn)| |
| ピーク| Θ(1)| |
| カウント| Θ(1)| |
| クリア| Θ(N)| |
| 含む| Θ(N)| |
| CopyTo | Θ(N)| Array.Copyを使用します。実際の複雑さは低くなる可能性があります|
| ToArray | Θ(N)| Array.Copyを使用します。実際の複雑さは低くなる可能性があります|
| GetEnumerator | Θ(1)| |
| Enumerator.MoveNext | Θ(1)| |
コレクションは「安定」する必要がありますか? つまり、IComparisonが等しい2つのアイテムが必要です。
'Construct Using IEnumerable'の複雑さをΘ(n)に修正しました。 @svickに感謝します。
| 操作| 複雑さ|
| --- | --- |
| IEnumerableを使用して構築する| Θ(logn)|
これはΘ(n)であるべきだと思います。 少なくとも入力を繰り返す必要があります。
+1
GetEnumeratorの呼び出し中およびforeachの使用時に追加のヒープ割り当てを回避するために、ネストされたパブリック列挙子構造を使用する必要がありますか? キューを介して列挙することはまれな操作であるため、私の仮定はノーです。
構造体列挙子を使用するQueue<T>
と一貫性を保つために、構造体列挙子を使用することに傾倒します。 また、構造体列挙子が現在使用されていない場合、将来使用するようにPriorityQueue<T>
を変更することはできません。
また、おそらくバッチ挿入の方法ですか? それが役立つ場合は、最初から開始するのではなく、常に前の挿入ポイントから並べ替えて続行できますか?:
public void Enqueue(List<T> items) {
items.Sort(_comparer);
... insertions ...
}
ここに最初の実装のコピーを投げまし
いいね。 いくつかの初期フィードバック:
Queue<T>.Enumerator
はIEnumerator.Reset
明示的に実装します。 PriorityQueue<T>.Enumerator
も同じようにすべきですか?Queue<T>.Enumerator
は、 _index == -2
を使用して、列挙子が破棄されたことを示します。 PriorityQueue<T>.Enumerator
にも同じコメントがありますが、追加の_disposed
フィールドがあります。 余分な_disposed
フィールドを削除し、 Queue<T>
_index == -2
を使用して、構造体を小さくし、 Queue<T>
との整合性を保つために破棄されたことを示します。_emptyArray
フィールドを削除して、代わりにArray.Empty<T>()
に置き換えることができると思います。また...
Dictionary<TKey, TValue>
、 HashSet<T>
、 SortedList<TKey, TValue>
、 SortedDictionary<TKey, TValue>
、 SortedSet<T>
など)では、nullを比較者に渡されます。この場合、 Comparer<T>.Default
が使用されます。また...
ToArray
は、新しい配列を割り当てる前に_size == 0
チェックすることで最適化できます。この場合、 Array.Empty<T>()
返すだけです。@justinvp素晴らしいフィードバック、ありがとう!
issue-574ブランチのebickle / corefxにコードを移行しました。
Array.Emptyを実装しました
dotnet / corefx#966の発行が完了すると修正されます。
フィードバックを求めている重要な領域の1つは、ToArray、CopyTo、および列挙子の処理方法です。 現在、パフォーマンスが最適化されています。つまり、ターゲットアレイはヒープであり、比較対象によって並べ替えられていません。
3つのオプションがあります。
1)そのままにして、返された配列がソートされていないことを文書化します。 (これは優先キューであり、ソートされたキューではありません)
2)メソッドを変更して、返されたアイテム/配列を並べ替えます。 それらはO(n)演算ではなくなります。
3)メソッドと列挙可能なサポートを完全に削除します。 これは「純粋な」オプションですが、優先キューが不要になったときに、残りのキューに入れられたアイテムをすばやく取得する機能が削除されます。
フィードバックが必要なもう1つの点は、同じ優先度の2つのアイテムに対してキューを安定させる必要があるかどうかです(結果を0と比較してください)。 通常、優先度付きキューは、同じ優先度を持つ2つのアイテムがキューに入れられた順序でデキューされることを保証しませんが、System.Reactive.Coreで使用される内部優先度付きキューの実装では、このプロパティを確保するために追加のオーバーヘッドが必要であることに気付きました。 私の好みはこれをしないことですが、開発者の期待の観点からどちらのオプションが優れているかは完全にはわかりません。
私も.NETに優先キューを追加することに興味があったので、このPRに遭遇しました。 誰かがこの提案をする努力をしたのを見てうれしいです:)。 コードを確認した後、私は次のことに気づきました。
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デキューに何らかの形式の自動配列縮小を追加することを検討しましたが、 @ svickが指摘したように、同様のデータ構造のリファレンス実装には存在しません。 .NET Core / BCLチームの誰かから、そのスタイルの実装を選択した特別な理由があるかどうか聞いてみたいと思います。
更新:リストをチェックしました
エンキューはnullをサポートする必要があり、nullを許可するものとして文書化されています。 バグに遭遇しましたか? そのエリアのユニットテストエリアがどれほど堅牢であるかはまだ思い出せません。
興味深いことに、 @ svickによってリンクされた参照ソースで、 Queue<T>
_ShrinkThreshold
という名前の未使用のプライベート定数があることに気付きました。 おそらく、その動作は以前のバージョンに存在していました。
Contains
の実装でEquals
IComparer
代わりに次の単体テストを作成しました:
@mbeidler良い点。 MSDNによると、IComparer
ただし、他のコレクションクラスにも同じ問題が存在するように見えます。 SortedListを操作するようにコードを変更した場合
おそらく、これは既存のコレクションクラスのバグでもあります。 残りのコレクションクラスを掘り下げて、IComparerを受け入れるが、同等性を個別にテストする他のデータ構造があるかどうかを確認します。 ただし、優先キューの場合、平等から完全に独立したカスタム順序付けの動作が期待されます。
修正とテストケースをフォークブランチにコミットしました。 含むの新しい実装は、リストの動作に直接基づいています
今日しばらくしてから、他の組み込みコレクションのバグレポートを提出する可能性があります。 おそらくリグレッション動作のために修正できませんが、少なくともドキュメントを更新する必要があります。
ContainsKey
がIComparer<TKey>
実装を使用することは理にかなっていると思います。それが、キーを指定するものだからです。 ただし、線形検索でIComparable<TValue>
代わりにEquals
を使用する方が、 ContainsValue
方が論理的だと思います。 ただし、この場合、型の自然順序が等しいと矛盾する可能性がはるかに低いため、スコープは大幅に縮小されます。
については、MSDNのドキュメントでそれを表示されますSortedList<TKey, TValue>
のために、備考セクションContainsValue
TValueのソート順序は平等の代わりに使用されていることを示しありません。
@terrajobstこれまでのAPI提案についてどう思いますか? これはCoreFXに適していると思いますか?
:+1:
これを提出していただきありがとうございます。 この提案を正式にレビューするのに十分なデータがあると思うので、「APIレビューの準備ができました」というラベルを付けました。
Dequeue
とPeek
はメソッドをスローしているため、呼び出し元は各呼び出しの前にカウントを確認する必要があります。 代わりに(またはさらに)並行コレクションのパターンに従ってTryDequeue
とTryPeek
提供することは理にかなっていますか? 既存のジェネリックコレクションに非スローメソッドを追加することには未解決の問題があるため、これらのメソッドを持たない新しいコレクションを追加することは逆効果のようです。
@andrewgmorris関連https://github.com/dotnet/corefx/issues/4316「TryDequeueをキューに追加」
これについて基本的なレビューを行い、フレームワークにProrityQueueが必要であることに同意します。 ただし、その設計と実装を推進するのを手伝ってくれる人を雇う必要があります。 問題を把握した人は誰でも、 取り組むことができます。
では、APIにはどのような作業が残っていますか?
:これは、上記のAPIの提案から欠落しているPriorityQueue<T>
実装する必要がありますIReadOnlyCollection<T>
と一致するようにQueue<T>
( Queue<T>
今すぐ実装IReadOnlyCollection<T>
.NETのように4.6)。
配列ベースの優先キューが最適かどうかはわかりません。 .NETでのメモリ割り当ては非常に高速です。 古いmallocが処理したのと同じsearch-small-blocksの問題はありません。 ここから私の優先キューコードを使用することを歓迎します: https :
@ebickle_speclet_の1つの小さなニット。 それは言う:
/// 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.
@SunnyWarEnqueueメソッドのドキュメントを修正しました。ありがとうございます。
少し前に、この時点で共有することにしたスキップリストデータ構造に基づいて、優先度付きキューと同様の複雑さを持つデータ構造を作成しました: https :
https://en.wikipedia.org/wiki/Skip_list
スキップリストは、Containsが平均的なケースO(log(n))
あることを除いて、平均的なケースで上記の優先度キューの複雑さに一致します。 さらに、最初または最後の要素へのアクセスは一定時間の操作であり、順方向と逆方向の両方での反復は、PQの順方向の複雑さに一致します。
このような構造には明らかにメモリのコストが高くなるという欠点があり、 O(n)
最悪の場合の挿入と削除に発展するため、トレードオフがあります...
これはすでにどこかに実装されていますか? 予定されているリリースはいつですか?
また、既存のアイテムの優先度を更新するのはどうですか?
@ Priya91 @ianhaysこれはレビューの準備ができているとマークする準備ができていますか?
これは上記のAPI提案にはありません:PriorityQueue
IReadOnlyCollectionを実装する必要があります キューに一致する (列 IReadOnlyCollectionを実装するようになりました .NET 4.6以降)。
ここで@justinvpに同意します。
@ Priya91 @ianhaysこれはレビューの準備ができているとマークする準備ができていますか?
そうだと思います。 これはしばらくの間座っていました。 その上で動きましょう。
@justinvp @ianhaysIReadOnlyCollectionを実装するように仕様を更新しました
クラスの完全な実装があり、配列ベースの実装を使用するそれに関連付けられたPriorityQueueDebugViewがあります。 ユニットテストはまだ100%の範囲ではありませんが、興味があれば、少し作業をしてフォークからほこりを払うことができます。
@NKnuspererは、既存のアイテムの優先度を更新することについて良い点を述べました。 とりあえず省略しますが、スペックレビューの際に検討する必要があります。
完全なフレームワークには2つの実装があり、それらは可能な代替手段です。
https://referencesource.microsoft.com/#q = priorityqueue
参考までに、stackoverflowhttp://stackoverflow.com/questions/683041/java-how-do-i-use-a-priorityqueueのJavaPriorityQueueに関する質問があります。 優先度が単なるint優先度ラッパーオブジェクトではなく、比較子によって処理されるのは興味深いことです。 たとえば、キュー内のアイテムの優先度をすでに簡単に変更することはできません。
APIレビュー:
CoreFXがこのタイプを使用することを期待しているため、CoreFXにこのタイプがあると便利であることに同意します。
APIシェイプの最終レビューのために、サンプルコードPriorityQueue<Thread>
とPriorityQueue<MyClass>
ます。
T
によってのみ暗示されています。ノート:
Remove
とAdd
になります。これは、CoreFXで使用するのに非常に便利なタイプです。 これをつかむことに興味がある人はいますか?
優先度付きキューをバイナリヒープに固定するという考えは好きではありません。 詳細については、私のAlgoKitwikiページをご覧ください。 簡単な考え:
昨年、私はいくつかのヒープタイプを実装しました。 特に、優先キューとして使用できるIHeap
インターフェイスがあります。
私はすべて、 IHeap
インターフェイスといくつかの最もパフォーマンスの高い実装(少なくともそれらの配列ベース)を紹介することに賛成です。 APIと実装は、上記でリンクしたリポジトリにあります。
したがって、_priorityqueues_はありません。 ヒープ。
どう思いますか?
@karelz @safern @danmosemsft
@pgolebiowski使いやすく、非常にパフォーマンスの高いAPIを設計していることを忘れないでください。 PriorityQueue
(これはコンピュータサイエンスの用語として確立されています)が必要な場合は、ドキュメント/インターネット検索で見つけることができます。
基礎となる実装がヒープ(私は個人的になぜだろうか)、または他の何かである場合、それはそれほど重要ではありません。 実装が単純な代替案よりも測定可能に優れていることを実証できると仮定します(コードの複雑さも、いくらか重要なメトリックです)。
したがって、ヒープベースの実装( IHeap
APIなし)がいくつかの単純なリストベースまたは配列チャンクベースの実装よりも優れているとまだ信じている場合は、その理由を説明してください(理想的にはいくつかの文/段落で) )、実装アプローチについて合意を得ることができるようにします(そして、PRレビュー時に拒否される可能性のある複雑な実装であなたの側で時間を無駄にすることを避けます)。
ICollection
、 IEnumerable
ドロップしますか? ジェネリックバージョンを用意するだけです(ただし、ジェネリックIEnumerable<T>
はIEnumerable
をもたらします)
@pgolebiowskiの実装方法によって、外部APIが変更されることはありません。 PriorityQueue
は、動作/契約を定義します。 一方、 Heap
は特定の実装です。
基礎となる実装がヒープ(私は個人的になぜだろうか)、または他の何かである場合、それはそれほど重要ではありません。 実装が単純な代替案よりも測定可能に優れていることを実証できると仮定します(コードの複雑さも、いくらか重要なメトリックです)。
したがって、ヒープベースの実装がいくつかの単純なリストベースまたは配列チャンクのリストベースの実装よりも優れているとまだ信じている場合は、その理由を説明してください(理想的には数文/段落で)。実装アプローチ[...]
わかった。 優先度付きキューは、何らかの方法で実装できる抽象的なデータ構造です。 もちろん、ヒープとは異なるデータ構造で実装することもできます。 しかし、これほど効率的な方法はありません。 結果として:
私の言葉を裏付けるために、理論的なサポートから始めましょう。 アルゴリズム入門、コルメン:
[…]優先度キューには、最大優先度キューと最小優先度キューの2つの形式があります。 ここでは、最大優先度キューを実装する方法に焦点を当てます。このキューは、最大ヒープに基づいています。
優先キューはヒープであると明確に述べました。 これはもちろんショートカットですが、あなたはその考えを理解します。 ここで、さらに重要なこととして、他の言語とその標準ライブラリが、説明している操作のサポートをどのように追加するかを見てみましょう。
PriorityQueue<T>
—ヒープで実装された優先キュー。BinaryHeap
—APIで明示的にヒープします。 ドキュメントには、バイナリヒープで実装された優先キューであると記載されています。 しかし、APIは構造、つまりヒープについて非常に明確です。CFBinaryHeap
—繰り返しになりますが、データ構造が何であるかを明示的に示し、「優先度付きキュー」という抽象的な用語の使用を避けます。 クラスについて説明しているドキュメント:バイナリヒープは、優先度付きキューとして役立ちます。 私はそのアプローチが好きです。priority_queue
—繰り返しになりますが、配列の上部に構築されたバイナリヒープを使用して正規に実装されています。heapq
—ヒープはAPIで明示的に公開されています。 優先度付きキューについては、ドキュメントでのみ説明されています。ヒープは二分木です。 また、ヒープが優先度付きキューに等しく、ヒープが二分木に等しいという大きなショートカットにも注目してください。heap package
—ヒープインターフェイスもあります。 明示的な優先度付きキューはありませんが、ドキュメントにのみあります。ヒープは、優先度付きキューを実装する一般的な方法です。 Rust / Swift / Python / Goの方法でヒープを明示的に公開し、優先度付きキューとして使用できることをドキュメントに明確に記載する必要があると強く信じています。 このアプローチは非常にクリーンでシンプルだと強く信じています。
ヒープデータ構造を明確に公開し、ドキュメントでのみ優先度付きキューの参照を作成するというアプローチを皆さんが気に入ってくれることを願っています。
上記の問題に同意する必要がありますが、別のトピック、つまりこれを実装する方法から始めましょう。 私はここで2つの解決策を見ることができます:
ArrayHeap
クラスを提供するだけです。 名前を見ると、処理しているヒープの種類がすぐにわかります(ここでも、ヒープの種類は数十あります)。 配列ベースであることがわかります。 あなたはすぐにあなたが扱っている獣を知っています。IHeap
インターフェイスを提供し、1つ以上の実装を提供することです。 顧客はインターフェースに依存するコードを書くことができます—これは複雑なアルゴリズムの本当に明確で読みやすい実装を提供することを可能にします。 DijkstraAlgorithm
クラスを書くことを想像してみてください。 IHeap
インターフェイス(1つのパラメーター化されたコンストラクター)に依存するか、 ArrayHeap
(デフォルトのコンストラクター)を使用するだけです。 「優先キュー」という用語を使用しているため、明確で、単純で、明示的で、あいまいさがありません。 そして、理論的に非常に理にかなっている素晴らしいインターフェース。上記のアプローチでは、 ArrayHeap
は、配列として格納された、暗黙のヒープ順の完全なd-aryツリーを表します。 これを使用して、たとえばBinaryHeap
またはQuaternaryHeap
を作成できます。
さらに議論するために、このペーパーをご覧になることを強くお勧めします。 あなたはそれを知っているでしょう:
4項ヒープ(4進)は、2項ヒープ(2進)よりも単純に高速です。 このホワイトペーパーでは多くのテストが行われています。 implicit_4
とimplicit_2
(場合によってはimplicit_simple_4
とimplicit_simple_2
)のパフォーマンスを比較することに興味があります。
実装の最適な選択は、入力に大きく依存します。 暗黙のd-aryヒープ、ペアリングヒープ、Fibonacciヒープ、二項ヒープ、明示的なd-aryヒープ、ランクペアリングヒープ、地震ヒープ、違反ヒープ、ランク緩和された弱いヒープ、および厳密なFibonacciヒープのうち、次のタイプはさまざまなシナリオのほぼすべてのニーズに対応します。
IHeap
アプローチを採用する場合、これを追加する価値があります。多くの場合、配列ベースのソリューションよりも非常に高速です。どちらの場合も、プログラミングの労力は驚くほど少ないです。 私の実装を見てください。
@karelz @benaadams
これは、CoreFXで使用するのに非常に便利なタイプです。 これをつかむことに興味がある人はいますか?
@safernこれからこれを手に入れてとても嬉しいです。
OK、それは私のキーボードと私の椅子の間の問題でした- PriorityQueue
はもちろんHeap
基づいています-私はそれが意味をなさないQueue
だけを考えていましたそして、ヒープが「ソート」されていることを忘れました。ロジック、アルゴリズム、チューリングマシンなどを愛する私のような人にとって、非常に恥ずかしい思考プロセスの停止です。お詫びします。 (BTW:Javaドキュメントのリンクにあるいくつかの文を読むとすぐに、矛盾がすぐにクリックされました)
その観点から、 Heap
上にAPIを構築することは理にかなっています。 ただし、そのクラスをまだ公開するべきではありません。CoreFXで必要なものである場合は、独自のAPIレビューと独自のディスカッションが必要になります。 実装によるAPIの表面クリープは望ましくありませんが、それは正しいことかもしれません。したがって、議論が必要です。
その観点から、まだIHeap
を作成する必要はないと思います。 後で良い決断かもしれません。
特定のヒープ(たとえば、上記の4-ary)が一般的なランダム入力に最適であるという研究がある場合は、それを選択する必要があります。 @safern @ ianhays @ stephentoubが意見を確認/
複数の実装されたオプションを使用した基礎となるヒープのパラメーター化は、IMOがCoreFXに属していないものです(ここでも間違っている可能性があります。他の人の考えを見てみましょう)。
私の理由は、IMOが間もなく数十億の特殊なコレクションを出荷するためです。これらのコレクションは、人々(アルゴリズムのニュアンスに強いバックグラウンドを持たない平均的な開発者)が選択するのが非常に困難です。 ただし、このようなライブラリは、あなた/コミュニティが所有するこの分野の専門家にとって、優れたNuGetパッケージになります。 将来的には、 PowerCollectionsに追加することを検討する可能性があります(過去4か月間、GitHubでこのライブラリを配置する場所と、所有する必要があるかどうか、またはコミュニティに所有を促す必要があるかどうかについて活発に話し合っています-この問題についてはさまざまな意見があります、2.0以降の運命を確定する予定です)
あなたがそれに取り組みたいようにあなたに割り当てる...
@pgolebiowskiコラボレーターの招待状が送信されました
@benaadams私はICollection
を維持します(穏やかな好み)。 CoreFXの他のdsとの一貫性のため。 IMOここに奇妙な獣を1つ持つ価値はありません...少数の新しい獣を追加する場合(たとえば、PowerCollectionsを別のリポジトリに追加する場合)、一般的でないものを含めるべきではありません...考え?
OK、それは私のキーボードと私の椅子の間の問題でした。
ハハ😄心配ありません。
ヒープの上にAPIを構築することは理にかなっています。 ただし、まだそのクラスを公開するべきではありません[...]実装によるAPIサーフェスのクリープは望ましくありませんが、それは正しいことかもしれません。したがって、議論が必要です。 [...]まだIHeapを作成する必要はないと思います。 後で良い決断かもしれません。
グループの決定がPriorityQueue
場合、私は設計を支援し、これを実装します。 ただし、ここでPriorityQueue
を追加すると、後でAPIでHeap
を追加するのが面倒になるという事実を考慮してください。どちらも、基本的に同じように動作します。 それは一種の冗長IMOでしょう。 これは私にとってデザインの匂いです。 優先キューは追加しません。 それは役に立ちません。
また、もう1つ考えました。 実際、ペアリングヒープのデータ構造はかなり頻繁に役立つ可能性があります。 配列ベースのヒープは、それらをマージするのが恐ろしいです。 この操作は基本的に線形です。 マージするヒープがたくさんある場合は、パフォーマンスが低下します。 ただし、配列ヒープの代わりにペアリングヒープを使用する場合、マージ操作は一定です(償却されます)。 これは、私が素晴らしいインターフェースと2つの実装を提供したい理由のもう1つの議論です。 1つは一般的な入力用、2つ目は特定のシナリオ用、特にヒープのマージが含まれる場合です。
IHeap
+ ArrayHeap
+ PairingHeap
投票してください! 😄(Rust / Swift / Python / Goのように)
ペアリングヒープが多すぎる場合-OK。 しかし、少なくともIHeap
+ ArrayHeap
行きましょう。 PriorityQueue
クラスを使用すると、将来の可能性がロックされ、APIが不明確になると思いませんか?
しかし、私が言ったように、提案されたソリューションに対してPriorityQueue
クラスに投票するなら、OKです。
共同編集者の招待状が送信されました-pingを受け入れると、それを割り当てることができます(GitHubの制限)。
@karelz ping :)
ここでPriorityQueueを追加すると、後でAPIでヒープを追加するのが面倒になるという事実を考慮してください。どちらも基本的に同じように動作するためです。 それは一種の冗長IMOでしょう。 これは私にとってデザインの匂いです。 優先キューは追加しません。 それは役に立ちません。
後で面倒になる理由について詳しく説明していただけますか? あなたの懸念は何ですか?
PriorityQueue
は、人々が使用する概念です。 そのように名前が付けられたタイプがあると便利ですよね?
Heap
の論理演算(少なくともそれらの名前)は異なるかもしれないと思います。 それらが同じである場合、最悪の場合、同じコードの2つの異なる実装を持つことができます(理想的ではありませんが、世界の終わりではありません)。 または、 PriorityQueue
親としてHeap
クラスを挿入できますか? (APIレビューの観点から許可されていると仮定します-現時点では理由はわかりませんが、APIレビューに関する長年の経験がないため、他の人が確認するのを待ちます)
投票とさらなる設計の議論がどのように進むか見てみましょう...私はIHeap
+ ArrayHeap
のアイデアにゆっくりとウォーミングアップしていますが、まだ完全には確信していません...
少数の新しいものを追加する場合...一般的でないものを含めるべきではありません
雄牛への赤いぼろきれ。 ICollection
削除できるように、他のコレクションを追加した人はいますか?
循環/リングバッファ; ジェネリックとコンカレント?
@karelz命名の問題の解決策は、DataFlowが農産物/消費者パターンに対して行うようなIPriorityQueue
ようなものである可能性があります。 優先キューを実装する多くの方法があり、それを気にしない場合はインターフェイスを使用します。 実装に注意するか、インスタンス使用実装クラスを作成しています。
後で面倒になる理由について詳しく説明していただけますか? あなたの懸念は何ですか?
PriorityQueue
は、人々が使用する概念です。 そのように名前が付けられたタイプがあると便利ですよね? […]私はIHeap
+ArrayHeap
のアイデアにゆっくりとウォーミングアップしていますが、まだ完全には確信していません...
@karelz私の経験から、抽象化( IPriorityQueue
またはIHeap
)を持つことが非常に重要であることがわかりました。 このようなアプローチのおかげで、開発者は分離されたコードを書くことができます。 (特定の実装ではなく)インターフェイスに対して記述されているため、柔軟性とIoCスピリットが向上します。 このようなコードの単体テストを作成するのは非常に簡単です(依存性注入を使用すると、独自のモックIPriorityQueue
またはIHeap
を注入して、どのメソッドがいつ、どの引数で呼び出されるかを確認できます)。 抽象化は良いです。
「優先キュー」という用語が一般的に使用されているのは事実です。 問題は、優先キューを効果的に実装する方法が1つしかないことです。それは、ヒープを使用することです。 たくさんの種類のヒープ。 だから私たちは持つことができます:
class ArrayHeap : IPriorityQueue {}
class PairingHeap : IPriorityQueue {}
class FibonacciHeap : IPriorityQueue {}
class BinomialHeap : IPriorityQueue {}
また
class ArrayHeap : IHeap {}
class PairingHeap : IHeap {}
class FibonacciHeap : IHeap {}
class BinomialHeap : IHeap {}
私にとっては、2番目のアプローチの方が見栄えがします。 それについてどう思いますか?
PriorityQueue
クラスについて—曖昧すぎて、そのようなクラスには完全に反対だと思います。 あいまいなインターフェースは良いですが、実装ではありません。 バイナリヒープの場合は、 BinaryHeap
と呼んでください。 それ以外の場合は、それに応じて名前を付けます。
SortedDictionary
ようなクラスの問題のため、私はすべてクラスに明確に名前を付けることにSortedList
とどのように違うのかについて、開発者の間で多くの混乱があります。 単にBinarySearchTree
と呼ばれたら、生活はもっと簡単になり、家に帰って子供たちに早く会うことができます。
ヒープにヒープという名前を付けましょう。
@benaadamsそれぞれが
最も可能性の高い結果(私はソリューションが好きなので、偏った主張)は、別のリポジトリを作成し、PowerCollectionsでプライミングしてから、チームからの基本的なガイダンス/監視でコミュニティに拡張させることです。
ところで: @terrajobstは、それは価値がないと考えており(「エコシステムには、より良く、より影響力のあることを行う」)、コミュニティがそれを完全に推進し(既存のPowerCollectionsから始めることを含む)、私たちの1
//私たちが決心する前にコミュニティがこの解決策に飛びつく機会があると思います;-)。 それは議論(そして私の好み)を無言にするでしょう;-)
@pgolebiowskiあなたは、 Heap
を持っている方がPriorityQueue
よりも優れているとゆっくりと私に納得させています-強力なガイダンスとドキュメントが必要です "これがPriorityQueue
やり方です- Heap
使用してください
ただし、CoreFXに複数のヒープ実装を含めることを非常に躊躇しています。 そこにある「通常の」C#開発者の98%以上は気にしません。 彼らはどちらが最良かを考えたくもありません、彼らはただ仕事を成し遂げるために何かを必要とします。 すべてのSWのすべての部分が高性能を念頭に置いて設計されているわけではありません。 すべての1回限りのツール、UIアプリなどについて考えてください。このdsがクリティカルパス上にある高性能のスケールアウトシステムを設計しているのでない限り、気にする必要はありません。
同じように、 SortedDictionary
、 ArrayList
またはその他のdsがどのように実装されているかは関係ありません。それらは適切に機能します。 私は(他の多くの人と同じように) 、シナリオでこれらのdsから高いパフォーマンスが必要な場合は、パフォーマンスを測定したり、実装を確認したりして、シナリオに十分かどうかを判断する必要があることを理解し
2%ではなく、98%のユースケースでユーザビリティを最適化する必要があります。 あまりにも多くのオプション(実装)を展開し、全員に決定を強いる場合、98%のユースケースで不必要な混乱を引き起こしました。 私はそれが価値があるとは思わない...
IMO .NETエコシステムは、非常に適切なパフォーマンス特性を備えた多くのAPI(コレクションだけでなく)の単一の選択肢を提供することに大きな価値があり、ほとんどのユースケースに役立ちます。 そして、それを必要とし、より深く掘り下げてより多くを学び、知識に基づいた選択とトレードオフを行うことをいとわない人々のために高性能の拡張を可能にするエコシステムを提供します。
とは言うものの、インターフェースIHeap
( IDictionary
やIReadOnlyDictionary
)を持つことは理にかなっているかもしれません-もう少し考えなければなりません/ APIレビューの専門家に聞いてくださいスペース ...
@pgolebiowskiがISet<T>
とHashSet<T>
話していることは、すでに(ある程度)わかっています。 私はそれをミラーリングすると言います。 したがって、上記のAPIはインターフェース( 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
番目の要素を取得して、もう一度プッシュします。IList<T>
は通常、次のような生意気な回避策です。APIが受け入れるコレクションに柔軟に対応したいので、それらを列挙したいが、 IEnumerable<T>
介して割り当てたくない。
のための一般的なインターフェースがないことに気づきました
ICollection<T>
{
int Count
CopyTo(T[], int index)
}
だからそれについては気にしないでください😢(IReadOnlyCollectionのようなものですが)
ただし、列挙子のリセットは明示的に実装する必要があります。これは、問題があり、スローする必要があるためです。
だから私の提案された変更
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
とIPriorityQueue
違いについては、まだ合意していません。メソッドの名前とロジックに少し影響します。 ただし、いずれにせよ、現在のAPI提案には次のものが欠けていることがわかります。
これらの操作は非常に重要であり、特に要素を更新する可能性があります。 これがないと、多くのアルゴリズムを実装できません。 ハンドルタイプを導入する必要があります。 ここのIHeapNode
です。 これは、 IHeap
方法を採用するための別の議論です。そうしないと、常にヒープノードになるPriorityQueueHandle
タイプを導入する必要があるためです...😜さらに、それが何を意味するのかあいまいです...一方、ヒープノード-誰もがその内容を知っており、自分が扱っているものを想像することができます。
実際、簡単に言えば、APIの提案については、このディレクトリをご覧ください。 おそらくそのサブセットだけが必要になるでしょう。 しかし、それでも、IMOに必要なものが含まれているだけなので、出発点として検討する価値があるかもしれません。
皆さん、どう思いますか?
IHeapNode
は、clrタイプのKeyValuePairと大差ありませんか?
しかし、それは優先順位とタイプを分離しているので、今ではPriorityQueue<TKey, TValue>
とIComparer<TKey> comparer
?
KeyValuePairは構造体であるだけでなく、そのプロパティは読み取り専用です。 基本的には、構造が更新されるたびに新しいオブジェクトを作成することと同じです。
キーの使用は、等しいキーでのみ機能しません。更新/削除する要素を知るには、より多くの情報が必要です。
/// <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
操作(非常に一般的です)に依存するアルゴリズムを実装できない裸の実装になります。 たとえば、ダイクストラのアルゴリズム、プリムのアルゴリズム。 または、ある種のスケジューラーを作成していて、あるプロセス(またはその他)がその優先順位を変更した場合、それに対処することはできません。
また、他のすべてのヒープ実装では、要素は明示的に単なるノードです。 他の場合は完全に自然です。ここでは少し人工的ですが、更新/削除するために必要です。
Javaでは、優先キューには要素を更新するオプションがありません。 結果:
AlgoKitプロジェクトにヒープを実装し、これらすべての設計上の問題に遭遇したとき、これが.NETの作成者がヒープ(または優先度付きキュー)などの基本的なデータ構造を追加しないことにした理由だと思いました。 どちらのデザインも良くないからです。 それぞれに欠点があります。
簡単に言えば、優先度による要素の効率的な順序付けをサポートするデータ構造を追加する場合は、正しい方法で実行し、要素の更新/削除などの機能を追加することをお勧めします。
配列内の要素を別のオブジェクトでラップすることに不安を感じる場合は、これだけが問題ではありません。 もう1つはマージです。 配列ベースのヒープでは、これは完全に非効率的です。 ただし...ペアリングヒープデータ構造(ペアリングヒープ:自己調整ヒープの新しい形式)を使用する場合、次のようになります。
実際、これは次の方法で解決できます。
IHeap
インターフェースを追加しますArrayHeap
とPairingHeap
をこれらすべてのハンドルで追加し、更新、削除、マージしますPriorityQueue
単なるラッパーであるArrayHeap
APIを簡素化し、PriorityQueue
はSystem.Collections.Generic
あり、すべてのヒープはSystem.Collections.Specialized
ます。
動作しますか?
APIレビューを通じて3つの新しいデータ構造を取得する可能性はほとんどありません。 ほとんどの場合、APIの量は少ない方が良いです。 不十分になった場合はいつでも後で追加できますが、APIを削除することはできません。
これが、私がHeapNodeクラスのファンではない理由の1つです。 そのようなことは内部のみである必要があり、APIは可能であれば既存の型を公開する必要があります-この場合はおそらくKVPです。
@ianhaysこれが内部のみに保持されている場合、ユーザーはデータ構造内の要素の優先度を更新できません。 これはほとんど役に立たず、Javaのすべての問題が発生することになります。つまり、ネイティブライブラリにすでに存在するデータ構造を再実装する人々です...私には悪いように思えます。 ノードを表す単純なクラスを持つよりもはるかに悪いです。
ところで:リンクリストには、ユーザーが適切な機能を使用できるようにノードクラスがあります。 これはほとんどミラーリングです。
ユーザーは、データ構造内の要素の優先順位を更新できません。
それは必ずしも真実ではありません。 優先度は、の代わりに次のような追加のデータ構造を必要としない方法で公開される可能性があります。
public override void Update(ArrayHeapNode<TKey, TValue> node, TKey key)
{
}
あなたは例えば
public override void Update(TKey item, TValue priority)
{
}
タイプパラメータとして優先度を公開することについても、私はまだ確信していません。 大多数の人がデフォルトの優先度タイプを使用することになり、TValueは不要な特異性になります。
void Update(TKey item, TValue priority)
まず、コードのTKey
とTValue
は何ですか? それはどのように機能するはずですか? コンピュータサイエンスの慣習は次のとおりです。
2番:
大多数の人がデフォルトの優先度タイプを使用することになり、TValueは不要な特異性になります。
「デフォルトの優先度タイプ」を定義してください。 PriorityQueue<T>
が欲しいだけだと思いますよね? その場合は、ユーザーが新しいクラス、優先度と値のラッパーを作成し、さらにIComparable
ようなものを実装するか、カスタムコンパレータを提供する必要があるという事実を考慮してください。 かなり貧しい。
まず、コード内のTKeyとTValueは何ですか?
アイテムとアイテムの優先度。 それらを優先度とその優先度に関連付けられたアイテムに切り替えることができますが、TKeyが必ずしも一意であるとは限らない(つまり、優先度の重複を許可する)コレクションがあります。 私はそれに反対していませんが、私にとってTKeyは通常一意性を意味します。
重要なのは、Nodeクラスを公開することは、Updateメソッドを公開するための要件ではないということです。
「デフォルトの優先度タイプ」を定義してください。 PriorityQueueが欲しいだけだと感じます
、そうですか?
はい。 このスレッドをもう一度読んでも、それが事実であると完全に確信しているわけではありません。
その場合は、ユーザーが新しいクラス、優先度と値のラッパーを作成し、さらにIComparableなどを実装するか、カスタムコンパレータを提供する必要があるという事実を考慮してください。 かなり貧しい。
あなたは間違っていません。 かなりの数のユーザーが、カスタムコンパレータロジックを使用してラッパータイプを作成する必要
.Netの他の部分との一貫性を保つために、タイプはHeap<T>
、 ArrayHeap<T>
、さらにはQuaternaryHeap<T>
ではなく、 PriorityQueue<T>
と呼ばれるべきだと思います。
List<T>
ではなく、 ArrayList<T>
ます。Dictionary<K, V>
ではなく、 HashTable<K, V>
ます。ImmutableList<T>
ではなくImmutableAVLTreeList<T>
ます。Array.Sort()
、ないArray.IntroSort()
。そのようなタイプのユーザーは通常、それらがどのように実装されているかを気にしません、それは確かにタイプについて最も目立つものであるべきではありません。
その場合は、ユーザーが新しいクラス、優先度と値のラッパーを作成し、さらにIComparableなどを実装するか、カスタムコンパレータを提供する必要があるという事実を考慮してください。
あなたは間違っていません。 かなりの数のユーザーが、カスタムコンパレータロジックを使用してラッパータイプを作成する必要があると思います。
サマリーAPIでは、比較は提供された比較者IComparer<T> comparer
によって提供され、ラッパーは必要ありません。 多くの場合、優先度は型の一部になります。たとえば、タイムスケジューラは、型のプロパティとして実行時間を持ちます。
カスタムIComparer
KeyValuePair
を使用しても、追加の割り当ては追加されません。 ただし、アイテムではなく値を比較する必要があるため、更新のために1つの余分な間接参照が追加されます。
アイテムがrefreturnを介して取得され、更新されてrefによってupdateメソッドに戻され、refが比較されない限り、構造体アイテムの更新は問題になります。 しかし、それは少し恐ろしいです。
@svick
.Netの他の部分との一貫性を保つために、タイプは
Heap<T>
、ArrayHeap<T>
、さらにはQuaternaryHeap<T>
ではなく、PriorityQueue<T>
と呼ばれるべきだと思います。
私は人類への信頼を失っています。
PriorityQueue
呼び出しは、.NETの他の部分と一貫性があり、 Heap
呼び出しはそうではありませんか? ヒープに何か問題がありますか? その場合、同じことがスタックとキューにも当てはまります。ArrayHeap
-> QuaternaryHeap
基本クラス。 大きな違い。List
はリストであるが、リストではないということなので、「リスト」はIMOに名前を付けるのにかなり悪い選択です。 😜.NETの他の部分との一貫性を保ち、このような問題が発生したいですか?
そして、そこで何がわかりますか... Jon Skeetは、実装をより厳密に反映しているため、 SortedDictionary
はSortedTree
と呼ばれるべきだと言っています。
@pgolebiowski
ヒープに何か問題がありますか?
はい、使用法ではなく、実装について説明しています。
その場合、同じことがスタックとキューにも当てはまります。
どちらも実際には実装を説明していません。たとえば、名前はそれらが配列ベースであることを示していません。
ArrayHeap
->QuaternaryHeap
基本クラス。 大きな違い。
はい、私はあなたのデザインを理解しています。 私が言っているのは、これのどれもユーザーに公開されるべきではないということです。
特定の設計パスに従った結果として、たくさんのものがやってくる。 スレッドを読み直してください。
私はスレッドを読みました。 デザインはBCLのものではないと思います。 「四次ヒープ」とは何か、使うべきかどうか疑問に思われるようなものではなく、使いやすく理解しやすいものが含まれているべきだと思います。
デフォルトの実装が彼らにとって十分ではない場合、それは他のライブラリ(あなた自身のような)が入ってくるところです。
ハッシュテーブル、ArrayList。 それらは存在しているようです。
はい、これらは.Net Framework 1.0クラスであり、誰も使用していません。 私の知る限り、それらの名前はJavaからコピーされたものであり、.Net Framework2.0の設計者はその規則に従わないことにしました。 私の意見では、それは正しい決断でした。
ところで、ユーザーの最初の考えは
List
はリストであるが、リストではないということなので、「リスト」はIMOに名前を付けるのにかなり悪い選択です。
です。 リンクリストではありませんが、同じことではありません。 そして、どこにでもArrayList
やResizeArray
(F#のList<T>
の名前)を書く必要がないのが好きです。
それは、ユーザーが何を扱っているかをすぐに示す意味のある名前を付けることです。
QuaternaryHeap
表示された場合、ほとんどの人は自分が何を扱っているのかわかりません。 一方、 PriorityQueue
が表示されている場合は、CSのバックグラウンドがなくても、何を扱っているのかが明確になっているはずです。 彼らは実装が何であるかを知りませんが、それがドキュメントの目的です。
@ianhays
まず、コード内のTKeyとTValueは何ですか?
アイテムとアイテムの優先度。 それらを優先度とその優先度に関連付けられたアイテムに切り替えることができますが、TKeyが必ずしも一意であるとは限らない(つまり、優先度の重複を許可する)コレクションがあります。 私はそれに反対していませんが、私にとってTKeyは通常一意性を意味します。
キー-優先順位付けを行うために使用されるもの。 キーは一意である必要はありません。 そのような仮定をすることはできません。
重要なのは、Nodeクラスを公開することは、Updateメソッドを公開するための要件ではないということです。
私はそれが次のように信じています:ネイティブライブラリでのDecrease-key / Increase-keyのサポート。 このトピックについても、データ構造の処理に関係するヘルパークラスがあります。
かなりの数のユーザーが、カスタムコンパレータロジックを使用してラッパータイプを作成する必要があると思います。 優先度付きキューに入れたい同等のタイプ、またはコンパレータが定義されたタイプをすでに持っているユーザーもかなりの数いると思います。 問題は、どちらの陣営が2つのうち大きいかです。
他のことはわかりませんが、優先度付きキューを使用するたびに、カスタムコンパレータロジックを使用してラッパーを作成するのはかなり恐ろしいことです...
別の考え-ラッパーを作成したとしましょう:
class Wrapper
{
public int Priority { get; set; }
public string SomeStuff ...
}
私はこのタイプでPriorityQueue<T>
を給餌します。 したがって、それに基づいて要素に優先順位を付けます。 いくつかのキーセレクターが定義されているとしましょう。 このような設計は、ヒープの内部作業をクラッシュさせる可能性があります。 要素の所有者であるため、いつでも優先度を変更できます。 ヒープを更新するための通知メカニズムはありません。 あなたはそれらすべてを処理し、それを呼び出す責任があります。 かなり危険で、私には簡単ではありません。
私が提案したハンドルの場合、タイプはユーザーの観点から不変でした。 そして、独自性に問題はありませんでした。
IMO IHeap
インターフェースを持ち、次にそのインターフェースを実装するクラスAPIを持つというアイデアが好きです。 私は@ianhaysとPriorityQueue
に焦点を当てたいので、顧客に異なる種類のヒープを提供するために3つの新しいAPIを公開するつもりはありません。
次に、値をキューに格納するためにNodeの内部/パブリッククラスを用意する必要はないと思います。 追加の割り当ては必要ありません。IDictionaryが行うことのようなものに従うことができ、Enumerableの値を生成するときにKeyValuePairを作成できます。PriorityQueue<T>
(この名前はそれに1つを与えるためだけのものです)。 このアプローチでは、BCL全体がIComparer<T>
で行うことに従い、その比較者と比較して優先順位を付けるコンストラクターを作成できます。 優先度をパラメーターとして渡すAPIを公開する必要はありません。
一部のAPIに優先順位を付けると、デフォルトの優先順位にしたい通常の顧客にとっては「使いにくく」なるか、より複雑になると思います。その場合、APIを与えるときに、その使用法を理解するのがより複雑になります。カスタムIComparer<T>
を使用するオプションが最も合理的であり、BCL全体のガイドラインに従います。
名前は彼らが行うことであり、彼らがそれを行う方法ではありません。
それらは、実装とその達成方法ではなく、抽象的な概念とユーザーのために達成するものにちなんで名付けられています。 (これはまた、それがより良いことが証明されれば、それらの実装を改善して別の実装を使用できることを意味します)
その場合、同じことがスタックとキューにも当てはまります。
Stack
は無制限のサイズ変更配列であり、 Queue
は無制限のサイズ変更循環バッファーとして実装されます。 それらは抽象的な概念にちなんで名付けられています。 スタック(抽象データ型) :後入れ先出し(LIFO)、キュー(抽象データ型) :先入れ先出し(FIFO)
ハッシュテーブル、ArrayList。 それらは存在しているようです。
DictionaryEntry
ええ、でもそうではないふりをしましょう。 それらは文明化されていない時代の遺物です。 プリミティブまたは構造体を使用する場合、型安全性とボックスはありません。 したがって、すべての追加に割り当てます。
@terrajobstのPlatformCompatibility Analyzerを使用すると、「しないでください」と表示されます。
リストはリストですが、リストではありません
これは実際には、シーケンスとも呼ばれるリスト(抽象データ型)です。
同様に優先度付きキューは、使用中に何を達成するかを示す抽象型です。 実装でそれがどのように行われるかではありません(多くの異なる実装が存在する可能性があるため)
たくさんの素晴らしい議論!
元の仕様は、いくつかのコア原則を念頭に置いて設計されました。
@karelz @pgolebiowski
「ヒープ」またはデータ構造の実装に合わせた別の用語に名前を変更しても、コレクションのほとんどのBCL規則と一致しません。 歴史的に、.NETコレクションクラスは、特定のデータ構造/パターンに焦点を合わせるのではなく、汎用になるように設計されてきました。 私の当初の考えは、.NETエコシステムの初期に、API設計者が意図的に「ArrayList」から「List」に移行したというものでした。
ヒープを使用する場合、同じことが発生する可能性があります。多くの中間スキルの開発者は、(悲しいことに)「ヒープ」を見て、「一般化されたデータ構造をヒープする」のではなく、アプリケーションのメモリヒープ(つまり、ヒープとスタック)と混同します。 System.Collections.Genericの普及により、ほぼすべての.NET開発者のインテリジェントな提案に表示され、なぜ新しいメモリヒープを割り当てることができるのか疑問に思うでしょう:)
比較すると、PriorityQueueははるかに発見しやすく、混乱しにくいです。 「キュー」と入力して、PriorityQueueのプロポーザルを取得できます。
整数の優先順位または優先順位の一般的なパラメーター(TKey、TPriorityなど)について尋ねられたいくつかの提案と質問。 明示的な優先度を追加するには、消費者が独自のロジックを作成して優先度をマッピングし、APIの複雑さを増す必要があります。 組み込みのIComparerを使用する
エントリが一意である必要がある場合、Enqueue()はArgumentExceptionをスローするために一意性ルックアップを必要とします。 さらに、アイテムを複数回キューに入れることを許可する有効なシナリオが存在する可能性があります。 この非一意性の設計により、どのオブジェクトが更新されているかを判断する方法がないため、Update()操作の提供が困難になります。 いくつかのコメントが示しているように、これは「ノード」参照を返すAPIに入り始め、ガベージコレクションが必要になる割り当てが(おそらく)必要になります。 これが回避されたとしても、優先キューの要素ごとのメモリ消費量が増加します。
ある時点で、仕様を投稿する前に、APIにカスタムIPriorityQueueインターフェイスがありました。 最終的に私はそれに反対することにしました-私が目指していた使用パターンは、エンキュー、デキュー、および反復でした。 すでに既存のインターフェースセットでカバーされています。 これを、内部でソートされたキューと考えてください。 アイテムがIComparerに基づいてキュー内で独自の(初期)位置を保持している限り
いくつかの顧客コードの例をお借りします。私の当初の計画は、PriorityQueueの既存のBCL実装を調べて、例の基礎として使用することでした。
サマリーAPIでは、比較は提供された比較者IComparerによって提供されます
比較者、ラッパーは必要ありません。 多くの場合、優先度は型の一部になります。たとえば、タイムスケジューラは、型のプロパティとして実行時間を持ちます。
提案されたOPAPIでは、クラスを使用するには、次のいずれかを満たす必要があります。
デュアルタイプAPIは、次の2つのオプションの負担を軽減することを目的としています。 すでに優先度が含まれているタイプがある場合、新しいPriorityQueue / Heapの構築はどのように機能しますか? APIはどのように見えますか?
私はそれが次のように信じています:ネイティブライブラリでのDecrease-key / Increase-keyのサポート。
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(log n)時間で)実行できます。
ただし、値と優先度しかわからない場合は、最初に遅い値(O(n))を見つける必要があります。
一方、効率的に更新する必要がない場合は、ヒープ内のアイテムごとに1つの割り当てが純粋なオーバーヘッドになります。
必要なときにだけそのオーバーヘッドを支払うソリューションが欲しいのですが、それを実装することができず、結果のAPIが見栄えが悪い可能性があります。
デュアルタイプAPIは、次の2つのオプションの負担を軽減することを目的としています。 すでに優先度が含まれているタイプがある場合、新しいPriorityQueue / Heapの構築はどのように機能しますか? APIはどのように見えますか?
これは良い点です。コンシューマーが値とは別に優先値(つまり整数)を既に維持しているシナリオでは、元のAPIにギャップがあります。 裏側はそれです
SortedList
私にとって、同じ原則がキューにも当てはまります。 歴史的に、キューは1次元のデータ構造です。
私の最近のC ++ stdライブラリの知識は確かに少し錆びていますが、std :: priority_queueでさえ、テンプレート化された(汎用)パラメータとしてプッシュ、ポップ、および比較子を使用しているように見えます。 C ++標準ライブラリのクルーは、パフォーマンスに敏感です:)
ちょうど今、いくつかのプログラミング言語で優先度付きキューとヒープの実装を非常にすばやくスキャンしました。C++、Java、Rust、Goはすべて、単一の型で動作します(ここに掲載されている元のAPIと同様)。 NPMで最も人気のあるヒープ/優先度キューの実装をざっと見てみると、同じことがわかります。
@pgolebiowskiは
ただし、これは、目的のパフォーマンス目標に一致し、受け入れても構わないと思っているトレードオフがある特定のデータ構造がわかっている場合に使用します。
一般に、フレームワークコレクションは、一般的な動作が必要な用途の90%をカバーします。 次に、非常に具体的な動作や実装が必要な場合は、おそらくサードパーティのライブラリを使用します。 実装にちなんで名前が付けられているので、ニーズに合っていることがわかります。
一般的な動作タイプを特定の実装に結び付けたくないだけです。 型名は同じままである必要があり、それらが一致しないために実装が変更された場合、それは奇妙です。
議論が必要な懸念はたくさんありますが、私たちが同意しない部分に現在最も影響を与えているもの、つまり要素の更新と削除から始めましょう。
では、これらの操作をどのようにサポートしますか? それらを含める必要があります、それらは基本的です。 Javaでは、設計者はそれらを省略しているため、次のようになります。
これはただ哀れです。 本当にそんなやり方を追求したい人はいますか? そのような無効化されたデータ構造を解放するのは恥ずかしいことです。
@pgolebiowskiは、ここにいる全員がプラットフォームに対して最善の意図を持っているので安心してください。 壊れたAPIを出荷したいと思う人はいません。 私たちは他人の過ちから学びたいので、そのような有用な関連情報(Javaストーリーに隣接するものなど)を持ち続けてください。
ただし、いくつか指摘したいことがあります。
提案されたアプローチが気に入らない場合、更新/削除をどのように設計したいかを尋ねました...これは他の人の話を聞いて、コンセンサスを探していると思います。
私はあなたの善意を疑うことはありません! 時々それはあなたがどのように尋ねるかが重要です-それは人々が反対側のテキストをどのように知覚するかに影響します。 テキストには感情がないので、書き留めると物事の理解が異なります。 第二言語としての英語は物事をさらに混乱させ、私たち全員がそれを認識する必要があります。 興味があれば、オフラインで詳細についてチャットさせていただきます...ここでの議論を技術的な議論に戻しましょう...
ヒープとPriorityQueueの議論についての私の2セント:どちらのアプローチも有効であり、明らかに長所と短所があります。
そうは言っても、「PriorityQueue」は既存の.NETアプローチとはるかに一貫しているようです。 今日のコアコレクションはリストです
1つのコレクションがここでトレンドに逆行するのは奇妙に感じるでしょう。
追加のコンストラクターを備えたPriorityQueueだと思います:PriorityQueue
その場合、PrioriryQueue
クラスArrayHeap:IHeap {}
クラスPairingHeap:IHeap {}
クラスFibonacciHeap:IHeap {}
クラスBinomialHeap:IHeap {}
わかった。 さまざまな声がたくさんあります。 私は再び議論を繰り返し、アプローチを洗練させました。 また、一般的な懸念にも対処します。 以下のテキストは、上記の投稿からの引用を利用しています。
このディスカッション全体の最終的な目標は、ユーザーを満足させるための特定の機能セットを提供することです。 ユーザーが多数の要素を持っていることはかなり一般的なケースであり、それらのいくつかは他よりも優先度が高くなっています。 最終的には、次の操作を効率的に実行できるように、この一連の要素を特定の順序で保持する必要があります。
他の標準ライブラリの作者もこの機能をサポートしようとしました。 このセクションでは、 Python 、 Java 、 C ++ 、 Go 、 Swift 、およびRustでどのように解決されたかについて説明します。
それらのいずれも、コレクションにすでに挿入されている要素の変更をサポートしていStackOverflow 。 これは、インターネットを介したそのような多くの質問の1つです。 デザイナーは失敗しました。
注目に値するもう1つの点は、すべてのライブラリがバイナリヒープの実装を通じてこの部分的な機能を提供することです。 さて、それは一般的な場合(ランダム入力)で最もパフォーマンスの高い実装ではないことが示されています。 一般的なケースに最適なのは、 4次ヒープ(配列として格納された暗黙のヒープ順の完全な4元ツリー)です。 これはあまり知られていません。これが、設計者が代わりにバイナリヒープを使用した理由である可能性があります。 しかし、それでも—重大度は低くなりますが、もう1つの悪い選択です。
それから何を学びますか?
私は私たちが提供すべきだと強く感じています:
IHeap<T>
インターフェースHeap<T>
クラスIHeap
インターフェースには、この投稿の冒頭で説明したすべての操作を実行するためのメソッドが含まれていることは明らかです。 クォータナリヒープで実装されたHeap
クラスは、98%のケースで頼りになるソリューションになります。 要素の順序は、コンストラクターに渡されたIComparer<T>
、または型がすでに比較可能な場合はデフォルトの順序に基づいています。
IHeap
を入力として受け取るロジックにカスタムヒープを挿入するだけで済みます。 特定の条件で4次ヒープよりも適切に動作する他のヒープの例としては、ペアリングヒープ、二項ヒープ、および最愛のバイナリヒープがあります。Heap
実装を使用するだけです。 これは、ユースケースの98%に向けて最適化されています。ISet
およびHashSet
IList
およびList
IDictionary
およびDictionary
Heap
は確かに明確で明確です。 また、 Stack
、 Queue
、 List
、およびArray
とも調和しています。 最新の標準ライブラリ(Go、Swift、Rust)でも、命名に関する同じアプローチが採用されました。これらは、ヒープを明示的に公開します。@pgolebiowski Heap<T>
/ IHeap<T>
がStack<T>
、 Queue<T>
、および/またはList<T>
ようにどのように命名されているのかわかりませんか? これらの名前はいずれも、内部でどのように実装されているかを説明していません(Tの配列が発生した場合)。
@SamuelEnglard
Heap
は、内部でどのように実装されているかも示していません。 多くの人にとって、ヒープが特定の実装の直後に続く理由がわかりません。 そもそも、同じAPIを共有するヒープには多くのバリエーションがあります。
ヒープを扱って4次ヒープを扱っていると言っても抽象的です。 T
配列に基づく暗黙的なデータ構造として実装できます( Stack<T>
、 Queue<T>
、およびList<T>
)または明示的(ノードとポインターを使用)。
簡単に言えば、 Heap<T>
はStack<T>
、 Queue<T>
、およびList<T>
Heap<T>
と非常によく似ています。これは、基本的な抽象的な構成要素である基本的なデータ構造であるためです。多くの方法で実装できます。 さらに、たまたま、それらはすべて、下にあるT
配列を使用して実装されています。 この類似性は本当に強いと思います。
それは理にかなっていますか?
ちなみに、ネーミングには無関心です。 C ++標準ライブラリの使用に慣れている人は、おそらく_priority_queue_を好むでしょう。 データ構造について教育を受けた人は、_ヒープ_を好むかもしれません。 投票する必要がある場合は、コイントスに近いですが、_ヒープ_を選択します。
@pgolebiowski私は私の質問を間違って言いました、それは私の悪いことです。 はい、 Heap<T>
はそれが内部でどのように実装されているかを述べていません。
はいヒープは有効なデータ構造ですが、ヒープ!=優先度付きキューです。 どちらも異なるAPIサーフェスを公開し、さまざまなアイデアに使用されます。 Heap<T>
/ IHeap<T>
は、(理論上の名前だけで) PriorityQueue<T>
/ IPriorityQueue<T>
によって内部的に使用されるデータ型である必要があります。
@SamuelEnglard
コンピュータサイエンスの世界がどのように組織されているかという点では、そうです。 抽象化のレベルは次のとおりです。
はい、 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
インターフェイスの余地がなく、優先度キューを4次ヒープの特定の実装に修正するという1つの解決策が残されています。 私はそれがあまりにも多くのレベルの抽象化を通過していると感じており、インターフェースを持つことのすべての素晴らしい利点を殺しています。
議論の途中からデザインの選択に戻りました。 PriorityQueue
とIPriorityQueue
ます。 しかし、基本的には次のようになります。
class BinaryHeap : IPriorityQueue {}
class PairingHeap : IPriorityQueue {}
class FibonacciHeap : IPriorityQueue {}
class BinomialHeap : IPriorityQueue {}
醜いだけでなく、概念的にも間違っていると感じます。これらは優先度付きキューのタイプではなく、同じAPIを共有していません( @SamuelEnglardがすでに述べたように)。 私たちはヒープに固執するべきだと思います。ヒープは、それらを抽象化するのに十分な大きさの家族です。 私たちは得るでしょう:
class BinaryHeap : IHeap {}
class PairingHeap : IHeap {}
class FibonacciHeap : IHeap {}
class BinomialHeap : IHeap {}
そして、私たちが提供するclass Heap : IHeap {}
。
ところで、誰かが次のことを役立つと思うかもしれません:
| Googleクエリ| 結果|
| :--------------------------------------:| :-----:|
| 「データ構造」「優先キュー」| 172,000 |
| 「データ構造」「ヒープ」| 430,000 |
| 「データ構造」「キュー」-「優先キュー」| 496,000 |
| 「データ構造」「キュー」| 530,000 |
| 「データ構造」「スタック」| 577,000 |
@pgolebiowski私はここを行ったり来たりしていると感じているので、認めます
@karelz @safern上記についてどう思いますか? 特定のAPI提案を提示できるように、 IHeap
+ Heap
アプローチを修正できますか?
ここでインターフェースの必要性に疑問を持っています( IHeap
またはIPriorityQueue
)。 はい、このデータ構造を実装するために理論的に使用できるさまざまなアルゴリズムがあります。 ただし、フレームワークに複数のフレームワークが付属する可能性は低く、さまざまな関係者が相互互換性のあるヒープ実装を作成する際に調整できるように、ライブラリの作成者が実際に正規のインターフェイスを必要とするという考えはあまりありそうにありません。
さらに、インターフェイスが解放されると、互換性のために変更することはできません。 つまり、インターフェイスは機能の点で具体的なクラスに遅れをとる傾向があります(今日のIListとIDictionaryの問題)。 対照的に、ヒープクラスがリリースされ、IHeapインターフェイスに対する深刻な要求があった場合、インターフェイスは問題なく追加できると思います。
@madelsonフレームワークは、ヒープの複数の実装を出荷する必要がない可能性が高いことに同意します。 ただし、その実装をインターフェイスでサポートすることにより、他の実装を気にする人は、別の実装(自分で作成したものまたは外部ライブラリ内)に簡単に交換でき、インターフェイスを受け入れるコードと互換性があります。
インターフェイスへのコーディングにマイナスの点は見当たりません。 それを気にしない人はそれを無視して、具体的な実装に直接行くことができます。 世話をする人は、自分で選んだ実装を使用できます。 それは選択です。 そしてそれは私が望む選択です。
@pgolebiowskiあなたが尋ねたので、ここに私の個人的な意見があります(私がいくつかの意見をチェックしたので、他のAPIレビューア/アーキテクトがそれを共有することをある程度確信しています):
名前はPriorityQueue
である必要があり、 IHeap
インターフェイスを導入しないでください。 実装は1つだけである必要があります(おそらくいくつかのヒープを介して)。
IHeap
インターフェイスは非常に高度なエキスパートシナリオです。PowerCollectionsライブラリ(最終的には作成される予定)またはその他のライブラリに移動することをお勧めします。
ライブラリとIHeap
インターフェースが非常に人気になった場合、後で考えを変えて、(コンストラクターのオーバーロードを介して)要求に基づいてIHeap
を追加できますが、それは有用/調整されていないと思いますBCLの残りの部分は、新しいインターフェイスを今すぐ追加することの複雑さを正当化するのに十分です。 単純なものから始めて、本当に必要な場合にのみ複雑なものに成長します。
...ちょうど私の2(個人)セント
意見の違いを考慮して、提案を前進させるために次のアプローチを提案します(今週初めに@ianhaysに提案したため、間接的に@safernに提案しました)。
...私の意見(あなたが求めたもの)について完全に透明にしようとしているので、落胆したり押し返したりしないでください-API提案がどのように行われるか見てみましょう。
@karelz
名前は
PriorityQueue
必要があります
何か議論はありますか? ただノーと言うので
以前はかなりうまく名前を付けたと思います。_入力がある場合は、それを提供し、データと証拠を添えてください。 また、他の人の議論に耳を傾け、それらを認めます。 同意しない場合は、他人の意見に反する証拠を提出してください。_
さらに、それは名前だけではありません。 それほど浅くはありません。 私が書いたものを読んでください。 それは、抽象化のレベル間で作業し、その上にコード/ビルドを強化できるようにすることです。
ああ、この議論には理由がありました:
ヒープを使用する場合、多くの中級の開発者は(悲しいことに)「ヒープ」を見て、「一般化されたデータ構造をヒープする」のではなく、アプリケーションのメモリヒープ(つまり、ヒープとスタック)と混同します。 [...]なぜ新しいメモリヒープを割り当てることができるのか疑問に思うでしょう。
😛
IHeap
インターフェースを導入すべきではありません。IHeap
インターフェイスは非常に高度なエキスパートシナリオです。PowerCollectionsライブラリに移動することをお勧めします。
@bendonoはこれについてとても素敵なコメントを書きました。 @safernは、インターフェースを使用して実装を
別の注意-インターフェイスをサードパーティのライブラリに移動することをどのように想像しているかわかりません。 開発者はどのようにしてそのインターフェースに対してコードを記述し、私たちの機能を使用できるでしょうか? 彼らは私たちの非インターフェース機能に固執するか、それを完全に無視することを余儀なくされるでしょう、他のオプションはありません、それは相互に排他的です。 言い換えれば、私たちのソリューションはまったく拡張可能ではなく、どちらの場合も同じアーキテクチャコアに依存する代わりに、無効化されたソリューションまたはサードパーティのライブラリのいずれかを使用する
しかし、繰り返しになりますが、あなたはこれについてうまくコメントしました:壊れたAPIを出荷したいと思う人は誰もいません。
意見の違いを踏まえ、以下のアプローチで提案を進めていきます。 [...] 2つの代替案を作成します[...]それをAPIレビューに持ち込み、そこで議論し、そこで決定を下しましょう。 そのグループの少なくとも1人がヒープの提案に投票しているのを見つけたら、私は自分の見解を再考したいと思います。
上記のAPIのさまざまな部分について多くの議論がありました。 それは取り組みました:
PriorityQueue
vs Heap
なぜあなたが考えている人々がこの議論に貢献できないのですか? なぜ代わりに再開する必要があるのですか?
ヒープを使用する場合、多くの中級の開発者は(悲しいことに)「ヒープ」を見て、「一般化されたデータ構造をヒープする」のではなく、アプリケーションのメモリヒープ(つまり、ヒープとスタック)と混同します。
ええ、これは私です。 私は完全にプログラミングを独学で学んでいるので、「 Heap
」が議論に入ったときに何が言われていたのかわかりません。 言うまでもなく、「物の山」というコレクションの観点からさえ、私にとっては、あらゆる方法で順序付けられていないことをより直感的に意味します。
私はこれが本当に起こって
何か議論はありますか? ただノーと言うのではなく、私が上で書いたことに対処してくれれば、少なくともいいでしょう。
私の答えを読むと、私が私の立場の重要な議論に言及していることに気付くでしょう。
スレッドでIMOを数回繰り返した同じ引数。 私はそれらを要約しました
。 私が書いたものを読んでください。
私はこのスレッドを常に積極的に監視していました。 私はすべての議論とポイントを読みました。 あなたや他の人のすべてのポイントを読んだ(そして理解した)にもかかわらず、これは私の要約された意見です。
あなたはどうやらそのトピックに情熱を注いでいます。 すばらしい。 しかし、それぞれの側から同様の議論を繰り返しているだけで、それ以上先に進むことはないという立場になっていると感じています。そのため、2つの提案を推奨し、より多くの経験からそれらについてより多くのフィードバックを得ました。 BCL APIレビューア(BTW:私はまだ経験豊富なAPIレビューアとして数えていません)。
開発者はどのようにしてそのインターフェースに対してコードを記述し、私たちの機能を使用できるでしょうか?
IHeap
高度なシナリオを気にする開発者は、サードパーティのライブラリからインターフェイスと実装を参照します。 私が言ったように、それが人気があることが判明した場合、後でCoreFXに移行することを検討できます。
幸いなことに、後でIHeap
を追加することは完全に実行可能です。基本的に、 PriorityQueue
コンストラクターのオーバーロードが1つだけ追加されます。
はい、それはあなたの観点からは理想的ではありませんが、それはあなたが将来重要であると考えるイノベーションを妨げるものではありません。 妥当な中間点だと思います。
なぜあなたが考えている人々がこの議論に貢献できないのですか?
APIレビューは、活発な議論、ブレーンストーミング、あらゆる角度からの重み付けを伴う会議です。 多くの場合、GitHubの問題を何度も繰り返すよりも生産的/効率的です。 dotnet / corefx#14354とその前身であるdotnet / corefx#8034を参照してください-あまりにも長い議論、追跡するのが難しい膨大な数の微調整を伴ういくつかの異なる意見、結論はありませんが、素晴らしい議論、またかなりの数の人々にとって重要な無駄な時間、私たちが座ってそれについて話し、合意に達するまで。
APIレビューアにすべてのAPIの問題を監視させるか、おしゃべりな問題だけでもうまく拡張できません。
なぜ代わりに再開する必要があるのですか?
二度と始めません。 どうしてそんなことを考えるのか?
最初のレベル(エリア所有者レベル)でAPIレビューを終了するには、賛否両論のある最も人気のある2つの提案を次のAPIレビューレベルに送信します。
これは、階層的な承認/レビューアプローチです。 これはビジネスレビューに似ています。意思決定権を持つVP / CEOは、社内のすべてのプロジェクトに関するすべての議論を監督するわけではありません。チーム/レポートに、最も影響力のある、または伝染性のある決定の提案を提出して、さらに議論するように依頼します。 チーム/レポートは、問題を要約し、代替ソリューションの長所/短所を提示する必要があります。
このスレッドではまだ述べられていないことがあるため、最後の2つの提案を賛否両論で提示する準備ができていないと思われる場合は、次のレビューでトップ候補が数人になるまで話し合いを続けましょう。 APIレビューレベル。
言わなければならないことはすべて言われたような気がしました。
意味がありますか?
名前は
PriorityQueue
必要があります何か議論はありますか?
私の答えを読むと、私が私の立場の重要な議論に言及したことがわかります。
ねえ...私は引用したものを参照していました(明らかに)-あなたはヒープの代わりに優先度付きキューを使うことに決めました。 そして、はい、私はあなたの答えを読みました-それはこれに対する議論の正確に0%を含んでいます。
私はこのスレッドを常に積極的に監視していました。 私はすべての議論とポイントを読みました。 あなたや他の人のすべてのポイントを読んだ(そして理解した)にもかかわらず、これは私の要約された意見です。
あなたは双曲線になるのが好きです、私は前にそれに気づきました。 あなたは単にポイントの存在を認めるだけです。
開発者はどのようにしてそのインターフェースに対してコードを記述し、私たちの機能を使用できるでしょうか?
IHeapの高度なシナリオを気にする開発者は、サードパーティのライブラリからインターフェイスと実装を参照します。 私が言ったように、それが人気があることが判明した場合、後でCoreFXに移行することを検討できます。
あなたはここで私の言葉を繰り返しているだけで、上記の結果として私が提示した問題にまったく取り組んでいないことを知っていますか? 私が書いた:
彼らは私たちの非インターフェース機能に固執するか、それを完全に無視することを余儀なくされるでしょう、他のオプションはありません、それは相互に排他的です。
私はあなたのためにこれを非常に明確にします。 2つの互いに素な人々のグループがあります:
ここでの問題は、私が言ったように、それらが互いに素な人々のグループであるということです。 また、共通のアーキテクチャコアがないため、コードベースは互換性がありません_。 後で元に戻すことはできません。
幸いなことに、後でIHeapを追加することは完全に実行可能です。基本的に、PriorityQueueにコンストラクターのオーバーロードが1つだけ追加されます。
私はそれがなぜダメなのかをすでに書いています:
なぜあなたが考えている人々がこの議論に貢献できないのですか?
APIレビューアにすべてのAPIの問題を監視させるか、おしゃべりな問題だけでもうまく拡張できません。
はい、APIレビューアがすべてのAPIの問題を監視できない理由を尋ねていました。 正確に言えば、あなたは確かにそれに応じて応答しました。
意味がありますか?
いいえ、私はこの議論に本当にうんざりしています。 それがあなたの仕事であり、あなたがそうするように言われているという理由だけで、あなたたちの何人かは明らかにそれに参加しています。 いつも指導が必要な方もいらっしゃいますが、とても面倒です。 コンピュータサイエンスのバックグラウンドが明らかに不足しているため、優先キューを内部にヒープを使用して実装する必要がある理由を証明するように依頼されました。 ヒープが実際に何であるかさえ理解していない人もいるため、議論はさらに混乱します。
要素の更新と削除を許可しない、無効にしたPriorityQueueを使用してください。 健全なオブジェクト指向アプローチを許可しない設計を採用してください。 拡張機能を作成するときに標準ライブラリを再利用できないソリューションを使用してください。 Javaの方法で行ってください。
そしてこれは...これは驚くべきことです:
ヒープを使用する場合、多くの中級の開発者は(悲しいことに)「ヒープ」を見て、「一般化されたデータ構造をヒープする」のではなく、アプリケーションのメモリヒープ(つまり、ヒープとスタック)と混同します。
APIにアプローチを提示します。 私は興味がある。
これが本当に起こっているなんて信じられない...
ええと、excuuuuuuuuuuuuuuuuuは、 Heap
がメモリヒープとは異なるある種のデータ構造であることを学ぶための適切なコンピュータサイエンス教育を受ける機会がなかったので、私を失礼します。
ただし、要点はまだ残っています。 何かのヒープは、それが何らかの形で注文されたことについてません。 処理するもののオブジェクトを格納できるコレクションが必要な場合、後で入ってくるインスタンスをもっと早く処理する必要があるかもしれませんが、 Heap
と呼ばれるものを検索することはありません。 一方、 PriorityQueue
は、まさにそれを実行していることを完全に伝えます。
バッキングの実装として? 確かに、実装の詳細は私の関心事ではありません。
いくつかのIHeap
抽象化? APIの作成者と、それがために使われているかを知るためにCS主を持ってない人のために大きい、理由はありませんしません。
その意図をあまりよく述べておらず、その後発見可能性を制限する不可解な名前を何かに与えるのですか? 👎
ええと、ヒープがメモリヒープとは異なるある種のデータ構造であることを学ぶために、適切なコンピュータサイエンスの教育を受ける機会がなかったので、私を失礼します。
ばかげてる。 同時に、そのような機能の追加に関するディスカッションに参加したいと考えています。 トローリングのように聞こえます。
何かのヒープは、それが何らかの形で注文されたことについては何も意味しません。
あなたは間違っている。 ヒープとして注文されます。 あなたがリンクした写真のように。
バッキングの実装として? 確かに、実装の詳細は私の関心事ではありません。
私はすでにそれに対処しました。 ヒープのファミリーは巨大であり、実装の上に2つのレベルの抽象化があります。 優先キューは、3番目の抽象化レイヤーです。
処理するもののオブジェクトを格納できるコレクションが必要な場合、後で入ってくるインスタンスをより早く処理する必要がある場合は、ヒープと呼ばれるものを検索しません。 一方、PriorityQueueは、まさにそれを実行することを完全に伝達します。
そして、背景がなければ、優先キューに記事を提供するようにGoogleに依頼しますか? まあ、私たちの意見では多かれ少なかれ何が起こりそうかを議論することができます。 しかし、非常にうまく言われているように:
入力がある場合は、それを提供し、データと証拠を添えてください。 同意しない場合は、他人の意見に反する証拠を提出してください。
そして、データによると、あなたは間違っています:
クエリ| ヒット
:----:|:----:|
| 「データ構造」「優先キュー」| 172,000 |
| 「データ構造」「ヒープ」| 430,000 |
データ構造について読んでいるときにヒープに遭遇する可能性はほぼ3倍です。 さらに、Swift、Go、Rust、およびPythonの開発者は、標準ライブラリがそのようなデータ構造を提供するため、よく知っている名前です。
クエリ| ヒット
:----:|:----:|
| 「golang」「優先キュー」| 3.390 |
| 「さび」「優先キュー」| 8.630 |
| 「swift」「priorityqueue」| 18.600 |
| 「python」「優先キュー」| 72.800 |
| 「golang」「ヒープ」| 79.000 |
| 「さび」「ヒープ」| 492.000 |
| 「swift」「heap」| 551.000 |
| "python" "ヒープ" | 555.000 |
ヒープデータ構造が前世紀のある時期に導入されたため、実際にはC ++でも同様です。
その意図をあまりよく述べておらず、その後発見可能性を制限する不可解な名前を何かに与えるのですか? 👎
意見はありません。 データ。 上記を参照。 特に経歴のない人からの意見はありません。 以前にデータ構造についてある程度読んでいなければ、優先キューをグーグルで検索することもできません。 そして、ヒープは多くのデータ構造101でカバーされています。
それはコンピュータサイエンスの基礎です。 初歩です。 アルゴリズムとデータ構造の学期が複数ある場合、ヒープは最初に目にするものです。
それでも:
編集: Googleトレンドを参照してください。
別の独学の開発者として、私は_heap_に問題はありません。 常に改善に努めている開発者として、私は時間をかけてデータ構造についてすべてを学び、理解してきました。 要するに、私は、命名規則が、彼らが属している分野の語彙を理解するのに時間をかけなかった人々を対象とするべきであるという含意に同意しません。
また、「名前はPriorityQueueにする
APIの命名について私たちがどのように考えているかについて説明します。
私たちは、他のほとんど何よりも.NETプラットフォーム内の一貫性を優先する傾向があります。 これは、APIを見慣れた予測可能なものにするために重要です。 これは、以前に使用した用語である場合、名前が100%正しくないことを受け入れることを意味する場合があります。
私たちの目標は、コンピュータサイエンスの正式な教育を受けていない開発者も含め、さまざまな開発者が利用できるプラットフォームを設計することです。 .NETが一般的に非常に生産的で使いやすいと認識されてきた理由の一部は、その設計ポイントに部分的に起因していると考えています。
私たちは一般的に、名前や用語がどれほど有名で確立されているかをチェックするときに「検索エンジンテスト」を採用しています。 ですから、 @ pgolebiowskiが行った調査に感謝します。 私は自分で調査を行ったことがありませんが、「ヒープ」は多くの非ドメイン専門家が探している用語ではないというのが私の直感です。
したがって、私は@karelzに同意する傾向があり、 PriorityQueue
がより良い選択のように
しかし、私はこれを指摘したいと思います:
人々の意見を聞きたくない場合は、オープンソースにしないでください。また、それを求めないでください。
誤った二分法です。 エコシステムや貢献者からのフィードバックを望まないわけではありません(もちろんそうです)。 しかし同時に、私たちの顧客ベースは非常に多様であり、GitHubの貢献者(または私たちのチームの開発者)がすべての顧客にとって常に最良のプロキシであるとは限らないことも理解する必要があります。 使いやすさは難しく、特にコレクションのような非常に人気のある分野では、.NETに新しい概念を追加するためにいくつかの反復が必要になる可能性があります。
@pgolebiowski :
私はあなたの洞察、データ、提案を高く評価します。 しかし、私はあなたの議論のスタイルに絶対に感謝しません。 あなたは私のチームのメンバーとこのスレッドのコミュニティメンバーの両方に対して個人的になりました。 あなたが私たちに同意しないからといって、それが「ただの私たちの仕事」であるという理由で私たちが専門知識を持っていない、または気にしないと非難する許可をあなたに与えません。 私たちの多くが文字通り世界中を移動し、この仕事をしたかったという理由だけで家族や友人を置き去りにしたことを考えてみてください。 あなたのようなコメントは非常に不公平であるだけでなく、デザインを進めるのにも役立ちません。
ですから、私は肌が厚いと思うのが好きですが、そのような行動にはあまり寛容ではありません。 私たちのドメインはすでに十分に複雑です。 対立的および敵対的なコミュニケーションを追加する必要はありません。
敬意を表してください。 アイデアを熱心に批判することは公正なゲームですが、人々を攻撃することはそうではありません。 ありがとうございました。
心を落ち着かせるすべての人へ、
私の厳しい態度によってあなたの幸せのレベルを下げてしまったことをお詫びします。
@karelz
技術的なミスをして、そこで自分を馬鹿にしました。 謝罪しました。 受け入れられました。 しかし、後で私に投げられました。 クールなIMOではありません。
私が書いたことがあなたを不幸にさせてすみません。 あなたがそれを説明したようにそれはそれほど悪くはありませんでし疲れ感に貢献した多くの要因の1つとして提供しました。 それほど深刻ではないと思います。 それでも、ごめんなさい。
そして、はい、誰もが間違いを犯します。 大丈夫です。 私も、例えば時々夢中になってしまいます。
私が最も得たのは、「あなたはただこれをするように言われている、あなたはそれを信じていない」ということでした-ええ、そういうわけで私は週末にもそれをします。
申し訳ありませんが、お疲れ様でした。ありがとうございました。 5/10のマイルストーンまでにあなたがどれほど特別に献身的であったかが私にはわかりました。
@terrajobst
あなたが私たちに同意しないからといって、それが「ただの私たちの仕事」であるという理由で私たちが専門知識を持っていない、または気にしないと非難する許可をあなたに与えません。
あなたのようなコメントは非常に不公平であり、デザインを進めるのにも役立ちません。
「なめて」と思って病気の方がいらっしゃいましたら、お気軽にどうぞ。
問題が発生し、対処しました。 誰もがそれから何かを学びました。 ここのみんながフレームワークの品質に関心を持っていることがわかります。それは絶対に素晴らしく、貢献する意欲を持っています。 今後ともCoreFXにご協力いただきますようお願い申し上げます。 そうは言っても、私はおそらく明日、あなたの新しい技術的インプットに取り組みます。
@pgolebiowski
いつか直接会えることを願っています。 私は正直に言って、すべてをオンラインで行うことの課題の一部は、どちらの側からも意図せずに性格が悪い方法で混ざり合う可能性があることだと信じています。
今後ともCoreFXにご協力いただきますようお願い申し上げます。 そうは言っても、私はおそらく明日、あなたの新しい技術的インプットに取り組みます。
こっちも一緒。 これは面白い空間で、一緒にできる素晴らしいことがたくさんあります:-)
@pgolebiowski最初に、返信ありがとう
私は私たちの関係で最初からやり直すことをお勧めします。 議論を技術的なものに戻しましょう。このスレッドから、将来同様の状況に対処する方法を学びましょう。相手がプラットフォームに最も関心を持っているだけだともう一度仮定しましょう。
ところで:これは過去9か月間のCoreFXリポジトリでのいくつかの難しい出会い/議論の1つであり、ご覧のとおり、私たち(特に私を含む)はまだそれらをうまく処理する方法を学んでいます-したがって、この特定のインスタンスは進行中です私たちにも利益をもたらし、それは私たちを将来より良くし、情熱的なコミュニティメンバーからのより良い異なる視点を理解するのに役立ちます。 多分それは貢献ドキュメントへの私達の更新を形作るでしょう...
私の厳しいコメントは、この議論の非効率性の結果でした。 非効率性=混沌とした議論の方法。ポイントが対処/解決されておらず、それにもかかわらず私たちはさらに進んでいます=>面倒です。
あなたの欲求不満を理解しました! 興味深いことに、同じ理由で反対側にも同様の欲求不満がありました😉...世界がどのように機能するかはほとんどおかしいです:)。
残念ながら、設計上の決定を下すとき、難しい議論は仕事の一部です。 たくさんの仕事です。 多くの人がそれを過小評価しています。 必要な重要なスキルは、すべての人に対する忍耐力と、自分の意見を超えて、うまくいかなくてもコンセンサスを推進する方法を考える能力です。 そのため、2つの提案を提案し、技術的な議論をAPIレビューアグループに「エスカレーション」することを提案しました(主に、自分が正しいかどうかわからないためですが、世界中の他のすべての開発者と同じように自分が正しいことを密かに望んでいます😉 )。
トピックについて意見を持ち、同じスレッドでコンセンサスに議論を進めることは非常に困難です。 その観点から、あなたと私はこのスレッドで最も一般的です-私たちは両方とも意見を持っていますが、私たちは両方とも議論を終結と決定に駆り立てようとしています。 それでは、緊密に協力しましょう。
私の一般的なアプローチは次のとおりです。誰かが私を攻撃していると思うときはいつでも、邪悪で、怠惰で、私を苛立たせている、または何かです。 私は最初に自分自身と特定の人に尋ねます:なぜですか? なんでそんな事を言ったの? どういう意味?
通常、それは動機の理解/コミュニケーションの欠如の兆候です。 または、行間を読みすぎて、侮辱/告発/悪意がないところを見る兆候。
技術的な質問について話し続けることを恐れていないので、以前に質問したかったことは次のとおりです。
要素の更新と削除を許可しない、無効にしたPriorityQueueを使用してください。
これは私にはわかりません。 IHeap
(ここの私の/元の提案)を省略した場合、なぜそれが不可能なのですか?
IMOは、クラス機能の観点から2つの提案に違いはありません。唯一の違いは、 PriorityQueue(IHeap)
コンストラクターのオーバーロードを追加するかどうかです(クラス名の争いを解決するための独立した問題として残します) 。
免責事項(誤解を避けるため):記事を読んだり調査したりする時間がありません。技術的な議論を推進したい人からのエレベーターピッチの議論を提示して、短い答えを期待しています。 注:私がトローリングしているわけではありません。 私は、この主張をしている私たちのチームの誰にでも同じ質問をします。 あなたがそれを説明する/議論を推進するエネルギーがない場合(あなたの側からの困難と時間の投資を考えると完全に理解できるでしょう)、ただそう言ってください、難しい感情はありません。 私や誰かからのプレッシャーを感じないでください(そしてそれはスレッドのすべての人に当てはまります)。
ここに不要なコメントをもう1つ追加しようとはしていませんが、このスレッドは長すぎます。 インターネット時代の一番のルールは、人間関係を気にするならテキストコミュニケーションを避けることです。 (まあ、私はそれを造った)。 他のオープンソースコミュニティは、必要性が明らかな場合、この種の議論のためにGoogleハングアウトに切り替えると思います。 他の人の顔を見ると、「侮辱的」なことは決して言わず、人はすぐにお互いに親しみます。 たぶん私たちも試すことができますか?
@karelz上記の議論の長さのため、フローが変更されていない場合、新しい人が貢献する可能性はほとんどありません。 そのため、私は今、次のアプローチを提案したいと思います。
それは理にかなっている可能性がありますか?
要素の更新と削除を許可しないPriorityQueue。
それは、これらの機能を欠いた元の提案に関するものでした:)明確にしないで申し訳ありません。
あなたがそれを説明する/議論を推進するエネルギーがない場合(あなたの側からの困難と時間の投資を考えると完全に理解できるでしょう)、ただそう言ってください、難しい感情はありません。 私や誰かからのプレッシャーを感じないでください(そしてそれはスレッドのすべての人に当てはまります)。
私はあきらめない。 痛みなしゲインなしxD
@ xied75
他のオープンソースコミュニティは、必要性が明らかな場合、この種の議論のためにGoogleハングアウトに切り替えると思います。 たぶん私たちも試すことができますか?
いいね ;)
Heap
/ IHeap
またはPriorityQueue
/ IPriorityQueue
(または他の何か)のどちらを使用する場合でも、提供しようとしている機能については...
_実装とともにインターフェースが必要ですか?_
@bendono
その実装をインターフェースでサポートすることにより、他の実装を気にする人は、(自分で作成した、または外部ライブラリーの)別の実装に簡単に交換でき、インターフェースを受け入れるコードと互換性があります。
それを気にしない人はそれを無視して、具体的な実装に直接行くことができます。 世話をする人は、自分で選んだ実装を使用できます。
@madelson
このデータ構造を実装するために理論的に使用できるさまざまなアルゴリズムがあります。 ただし、フレームワークに複数のフレームワークが付属する可能性は低く、さまざまな関係者が相互互換性のあるヒープ実装を作成する際に調整できるように、ライブラリの作成者が実際に正規のインターフェイスを必要とするという考えはあまりありそうにありません。
さらに、インターフェイスが解放されると、互換性のために変更することはできません。 つまり、インターフェイスは機能の点で具体的なクラスに遅れをとる傾向があります(今日のIListとIDictionaryの問題)。
@karelz
インターフェイスは非常に高度なエキスパートシナリオです。
ライブラリと
IHeap
インターフェースが非常に人気になった場合、後で考えを変えて、需要に基づいて(コンストラクターのオーバーロードを介して)IHeap
を追加できますが、それは有用ではないと思います。 BCLの残りの部分は、新しいインターフェイスを今すぐ追加することの複雑さを正当化するのに十分です。 単純なものから始めて、本当に必要な場合にのみ複雑なものに成長します。
👍と👎を使用して、これに投票します(それぞれインターフェースに賛成と反対)。 または、コメントを書いてください。 理想的には、APIレビューアが参加します。
インターフェイスの変更は難しいですが、拡張メソッド(および今後のプロパティ)を使用すると、インターフェイスの拡張や操作が簡単になります(LINQを参照)。
インターフェイスの変更は難しいですが、拡張メソッド(および今後のプロパティ)を使用すると、インターフェイスの拡張や操作が簡単になります(LINQを参照)。
それらは、インターフェース上で公に定義されたメソッドでのみ機能します。 つまり、最初に正しく理解することを意味します。
クラスが使用されて落ち着くまで、インターフェイスを少し保留することをお勧めします。その後、インターフェイスを導入します。 (インターフェースの形状に関する議論は別の問題です)
率直に言って、私が気にするのはインターフェースだけです。 しっかりした実装があればいいのですが、私(または他の誰か)はいつでも自分で作成することができます。
数年前、 HashSet<T>
とまったく同じ会話をしたことを覚えています。 マイクロソフトはHashSet<T>
を望んでいましたが、コミュニティはISet<T>
望んでいました。 私の記憶が正しければ、最初にHashSet<T>
、 ISet<T>
を取得しました。 インターフェースがないと、パブリックAPIを変更することは(多くの場合不可能ではないにしても)困難であるため、 HashSet<T>
は非常に制限されていました。
ISet<T>
非BCL実装が多数あることは言うまでもなく、現在SortedSet<T>
もあることに注意する必要があります。 私はパブリックAPIでISet<T>
を使用しましたが、感謝しています。 私のプライベート実装では、正しいと思う具体的な実装を使用できます。 また、何も壊すことなく、実装を別の実装に簡単に交換できます。 これは、インターフェースなしでは不可能です。
いつでも独自のインターフェースを定義できると言う人は、これを検討してください。 しばらくの間、BCLのISet<T>
が発生しなかったと仮定します。 これで、独自のインターフェイスIMySet<T>
と確実な実装を作成できます。 しかし、ある日、BCL HashSet<T>
がリリースされます。 ISet<T>
実装する場合としない場合がありますが、 IMySet<T>
は実装しません。 その結果、 IMySet<T>
実装としてHashSet<T>
をスワップインできません。
この悲劇をもう一度繰り返すのではないかと心配しています。
インターフェイスにコミットする準備ができていない場合は、具体的なクラスを導入するには時期尚早です。
意見の相違は重要だと思います。 数字を見るだけで、インターフェースを求める人が少し増えますが、それではあまりわかりません。 以前にディスカッションに参加したことがあるが、インターフェイスに関してまだ意見を表明していない他の人に聞いてみます。
@ebickle @svick @horaciojcfilho @paulcbetts @justinvp @akoeplinger @mbeidler @SirCmpwn @andrewgmorris @weshaggard @BrannonKing @NKnusperer @danmosemsft @ianhays @safern @VisualMelon @ Joe4evr @jcouv @ xied75
通知された人のために: _guys、あなたはあなたの入力を提供できますか? :+ 1:、:-1:で投票するだけでも、非常に役立ちます。 この問題のコメント(上記の4つの投稿)から読み始めることができます-インターフェースを提供することが良い考えであるかどうかについて議論しています(解決されるまで、今のところこの側面のみ)。
これらの人々の中にはAPIレビュー担当者もいるかもしれませんが、先に進む前に、この基本的な決定において彼らのサポートが必要だと思います。 @ karelz 、 @ terrajobst 、この側面を解決するのを手伝ってくれるように頼むことは可能ですか? 彼らは最終的にそれをレビューするので、彼らの意見は非常に価値があります-特定のアプローチをコミットする前に(または複数の提案を行う前に、この時点でそれを知ることは非常に役立ちます、それは面倒です彼らの決定を以前に知ることができるので、少し無意味です)。
個人的には、私はインターフェースを求めていますが、決定が異なる場合は、別の道をたどることができます。
APIレビュー担当者をディスカッションに引きずり込みたくありません-長くて面倒です。APIレビュー担当者がすべてを読み直したり、最後の重要な返信を決定したりするのは効率的ではありません(私はそれに夢中になっています) )。
2つの正式なAPI提案を作成し(そこにある「良い」例を参照)、それぞれの長所/短所を強調できるようになった時点だと思います。 その後、APIレビュー会議でそれらをレビューし、投票を考慮して推奨を行うことができます。 そこでの議論によっては(複数の意見がある場合)、戻ってきてTwitterの投票や追加のGH投票などを開始する場合があります。
ところで:APIレビューミーティングはほぼ毎週火曜日に開催されます。
それを開始するのに役立つように、提案は次のようになります。
`` `c#
名前空間System.Collections.Generic
{{
パブリッククラスPriorityQueue
:IEnumerable
{{
public PriorityQueue();
public PriorityQueue(intcapacity);
public PriorityQueue(IComparer
public PriorityQueue(IEnumerable
public PriorityQueue(IEnumerable
public PriorityQueue(intcapacity、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();
}
}
}
`` `
TODO:
UpdatePriority
シナリオがありません@karelz OK、やります、お楽しみに! :笑顔:
大丈夫。 私の知る限り、インターフェースやヒープはAPIレビューに合格しません。 そのため、たとえば4次ヒープを忘れて、少し異なる解決策を提案したいと思います。 以下のデータ構造は、 Python 、 Java 、 C ++ 、 Go 、 Swift 、およびRust (少なくともそれら)で見られるものとはいくつかの点で異なります。
主な焦点は、最適な複雑さと優れた実世界のパフォーマンスを維持しながら、正確性、機能性の観点からの完全性、および直感性にあります。
@karelz @terrajobst
ユーザーが多数の要素を持っていることはかなり一般的なケースであり、それらのいくつかは他よりも優先度が高くなっています。 最終的には、次の操作を効率的に実行できるように、この一連の要素を特定の順序で保持する必要があります。
まず、優先度付きキューの構築に焦点を当てます(要素の追加のみ)。 それが行われる方法は、ユーザーデータの種類によって異なります。
TKey
とTValue
は別々のオブジェクトです。TKey
は同等です。var queue = new PriorityQueue<int, string>();
queue.Enqueue(5, "five");
queue.Enqueue(1, "one");
queue.Enqueue(3, "three");
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");
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
メソッドを使用します。 今回は1つの引数( TValue
)のみを取ります。Enqueue(TKey, TValue)
はInvalidOperationException
スローする必要があります。Enqueue(TValue)
はInvalidOperationException
スローする必要があります。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 。TKey
とTValue
は別々のオブジェクトですが、同じタイプです。TKey
は同等です。var queue = new PriorityQueue<int, int>();
queue.Enqueue(5, 50);
queue.Enqueue(1, 10);
queue.Enqueue(3, 30);
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
メソッドの最初の使用で解決されます。優先キューの作成についてはすでに説明しました。 コレクションに要素を追加する方法も知っています。 次に、残りの機能に焦点を当てます。
優先度が最も高い要素に対して実行できる操作は2つあります。要素を取得するか削除するかです。
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などの同等のデータ構造では提供されていません。 おそらく他にもいくつかありますが、私はそれらをチェックしただけです。 結果? 失望した開発者:
ここには基本的に2つのオプションがあります。
ユーザーが優先キューに要素を追加するたびに、ハンドルが与えられます。
var handle = queue.Enqueue(42, "forty two");
Handleは、次のパブリックAPIを持つクラスです。
public class PriorityQueueHandle<TKey, TValue>
{
public TKey Key { get; }
public TValue Value { get; }
}
これは、優先キュー内の一意の要素への参照です。 効率が気になる場合は、 FAQをご覧
このようなアプローチにより、非常に直感的で簡単な方法で、優先度キュー内の一意の要素を簡単に変更できます。
/*
* 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);
上記の例では、特定のセレクターが定義されているため、ユーザーは自分でオブジェクトを更新できます。 優先キューは、それ自体を再配置するために通知する必要がありました(監視可能にしたくありません)。
ユーザーデータの種類が優先度付きキューの作成方法と要素の入力方法にどのように影響するかと同様に、更新方法も異なります。 今回はシナリオを短くします。おそらく、私が何を書くかはすでにご存知でしょう。
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)vs O(n + log n))。
TKey
はTValue
内に含まれています。var queue = new PriorityQueue<int, MyClass>(selector);
/* adding some elements */
queue.Update(handle);
このシナリオは、ハンドルセクションで例として示したものです。
上記はO(log n)で達成されます。 あるいは、ユーザーは、指定された要素と等しい最初の要素を見つけて内部再配置を行うメソッドUpdate(TValue)
を使用することもできます。 もちろん、これはO(n)です。
TKey
とTValue
は別々のオブジェクトですが、同じタイプです。ハンドルを使用すると、いつものようにあいまいさはありません。 更新を可能にする他の方法の場合—もちろんあります。 これは、単純さ、パフォーマンス、および正確さ(データを複製できるかどうかによって異なります)の間のトレードオフです。
ハンドル付きで、やはり問題ありません。 他の方法で更新する方が簡単な場合もありますが、パフォーマンスが低下し、常に正しいとは限りません(エントリが等しい)。
ハンドルを介して簡単かつ正確に行うことができます。 または、メソッド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
です。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
方が適しています。
ハンドルの使用に関しては効率に問題はありません。 共通の懸念は、内部で配列に基づくヒープを使用しているため、追加の割り当てが必要になることです。 恐れるな。 読む。
これは、優先キューを配信するためのまったく異なるアプローチになります。 初めて、標準ライブラリは、下の配列として表されるバイナリヒープとして実装されたこの機能を提供しません。 ペアリングヒープを使用して実装されペアリングヒープは、設計上、配列を使用しません。代わりに、単にツリーを表します。
ところで、それは最初のAPIレビューの反復のためです。 _openquestions_のセクションを解決する必要があります([そして可能であればAPIレビューアの]あなたの意見が必要です)。 最初の反復が少なくとも部分的に通過した場合、残りの議論では、新しい問題を作成したいと思います(これを閉じて参照してください)。
私の知る限り、インターフェースやヒープはAPIレビューに合格しません。
@pgolebiowski :では、問題を解決してみませんか? インターフェイスがないと、このクラスはほとんど役に立たなくなります。 私がそれを使用できる唯一の場所は、プライベート実装です。 その時点で、必要に応じて独自の優先度キューを作成(または再利用)できます。 別の実装と交換する必要があるとすぐに壊れてしまうため、署名にこのタイプのパブリックAPIをコードで公開することはできません。
@bendono
さて、マイクロソフトはここで最後の言葉を持っています、スレッドにコメントしている人も少なくありません。 そして、私たちはそれを知っています:
いくつかの意見を確認したので、他のAPIレビューア/アーキテクトがそれを共有することはある程度確信しています。名前はPriorityQueueである必要があり、IHeapインターフェイスを導入するべきではありません。 実装は1つだけである必要があります(おそらくいくつかのヒープを介して)。
これは@karelz、ソフトウェアエンジニアマネージャー、および@terrajobst、プログラムマネージャとこのリポジトリ+いくつかのAPIの審査の所有者によって共有されています。
以前の投稿で明確に述べたように、私は明らかにインターフェースを使用したアプローチが好きですが、この議論ではあまり力がないことを考えると、かなり難しいことがわかります。 私達は私達の主張をしました、しかし私達はほんの一部の論評者です。 とにかく、コードは私たちのものではありません。 他に何ができますか?
では、問題を解決してみませんか? インターフェイスがないと、このクラスはほとんど役に立たなくなります。
私は十分にやりました-私の仕事を嫌う代わりに、何かをします。 実際の作業を行います。 どうして私を責めようとするの? 自分の視点で他の人を説得することに成功しなかったことを自分のせいにしてください。
そして、そのようなレトリックを私に惜しまないでください。 それは本当に幼稚です-もっとうまくやってください。
ところで、この提案は、インターフェースを導入するかどうか、またはクラスの名前がPriorityQueue
かHeap
かに関係なく、一般的なことに焦点を当てています。 ですから、ここで本当に重要なことに焦点を当て、何かが必要な場合は行動へのバイアスを示してください。
@pgolebiowskiもちろん、それはマイクロソフトの決定です。 ただし、ニーズに合った使用したいAPIを提示することをお勧めします。 それが拒否された場合は、そうです。 提案を妥協する必要はないと思います。
あなたが私のコメントをあなたのせいにしたと解釈してくれたら、お詫びします。 それは確かに私の意図ではありませんでした。
@pgolebiowskiハンドルにKeyValuePair<TKey,TValue>
を使用してみませんか?
@SamuelEnglard
ハンドルに
KeyValuePair<TKey,TValue>
を使ってみませんか?
PriorityQueueHandle
は実際には_ペアリングヒープノード_です。 TKey Key
とTValue Value
2つのプロパティを公開します。 ただし、内部にははるかに多くのロジックがあり、それは単なる内部です。 これの私の実装を参照しKeyValuePair
は構造体であるため、毎回コピーされ、継承することはできません。PriorityQueueHandle
はかなり複雑なクラスであり、たまたまKeyValuePair
と同じパブリックAPIを公開しているということです。@bendono
ただし、ニーズに合った使用したいAPIを提示することをお勧めします。 それが拒否された場合は、そうです。 提案を妥協する必要はないと思います。
TKey
とTValue
を別々に持つことができない場合)、このクラスは本当に役に立たないことになります- -現在Javaで使用されているため。それは確かに私の意図ではありませんでした。
すみません、それでは私の間違いです。
デザインに関する私の質問(免責事項:これらの質問と説明、(まだ)ハードプッシュバックはありません):
PriorityQueueHandle
が必要ですか? キューに一意の値が含まれていると予想した場合はどうなりますか?Merge
が必要ですか? 他のコレクションにはありますか? APIを追加するべきではありません。実装が簡単であるという理由だけで、APIには一般的なユースケースがいくつかあるはずです。IEnumerable<KeyValuePair<TKey, TValue>>
から初期化を追加するだけで十分でしょうか? + Linqに依存するcomparer
オーバーロードが必要ですか? 常にデフォルトにフォールバックできますか? (免責事項:この場合、知識/専門知識が不足しているので、質問してください)keySelector
オーバーロードが必要ですか? 価値の一部として優先するか、それとも別のものにするかを決める必要があると思います。 個別の優先順位は私にはもう少し自然に思えますが、私は強い意見を持っていません。 私たちは賛否両論を知っていますか?個別/並行決定ポイント:
PriorityQueue
対Heap
IHeap
とコンストラクターのオーバーロードを導入しますか?IHeapとコンストラクターのオーバーロードを導入しますか?
物事が落ち着いて2セントを投入できるようになっているようです。個人的にはインターフェースが好きです。 私の意見では、構造を単純化し、最も使いやすくする方法で、API(およびそのAPIによって記述されるコア機能)から実装の詳細を抽象化します。
私があまり強く意見を持っていないのは、インターフェイスをPQueue / Heap / ILikeThisItemMoreThanThisItemListと同時に実行するのか、後で追加するのかということです。 APIは「流動的」である可能性があるため、フィードバックを得るまで最初にクラスとしてリリースする必要があるという議論は、確かに私が同意しない有効なものです。 問題は、インターフェイスを追加するのに十分な「安定性」があると見なされる場合になります。 上記のスレッドで、 IList
とIDictionary
は、かなり前に追加した正規の実装のAPIに遅れをとっていると言及されていたので、許容できる休止期間と見なされる期間はどれですか?
その期間を合理的な確実性で定義でき、それが許容できないほどブロックされていないことを確認できれば、インターフェイスなしでこの重いデータ構造のものを出荷することに問題はありません。 その後、その期間の後、使用法を調べて、インターフェースの追加を検討できます。
そして、はい、インターフェイスを追加することがマイクロソフトによって不利であると認識されていることは私にとって本当に驚くべきことです。
それは多くの点でそれが不利だからです。 インターフェイスを出荷すれば、ほぼ完了です。 そのAPIを反復する余地はあまりないので、最初は正しいことを確認し、今後数年間は正しいことを継続します。 いくつかの便利な機能が欠けていることは、すべてをより良くするこの1つの小さな変更だけを行うのが非常に良い、潜在的に不十分なインターフェイスで立ち往生するよりもはるかに良い場所です。
@karelzと@ianhaysの入力ありがとうございます!
本当に
PriorityQueueHandle
が必要ですか? キューに一意の値が含まれていると予想した場合はどうなりますか?動機:それはかなり複雑な概念のようです。 持っているのなら、なぜ必要なのか理解したいのですが? それはどのように役立ちますか? それとも、特定の実装の詳細がAPIサーフェスに漏れているだけですか? APIの複雑さに対して支払うために、それだけのパフォーマンスを購入するのでしょうか?
いいえ、必要ありません。 上で提案された優先キューAPIは非常に強力で、非常に柔軟性があります。 これは、要素と優先順位が重複する可能性があるという仮定に基づいています。 そのため、正しいノードを削除または更新するには、ハンドルが必要です。 ただし、要素が一意である必要があるという制限を設けると、内部クラス( PriorityQueueHandle
)を公開せずに、より単純なAPIを使用して上記と同じ結果を得ることができますが、これは実際には理想的ではありません。
一意の要素のみを許可すると仮定しましょう。 これまでのすべてのシナリオを引き続きサポートし、最適なパフォーマンスを維持できます。 よりシンプルな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を追加するべきではありません。実装が簡単であるという理由だけで、APIには一般的なユースケースがいくつかあるはずです。
同意。 必要ありません。 APIはいつでも追加できますが、削除することはできません。 このメソッドを削除し、(場合によっては)後で追加しても問題ありません(必要な場合)。
比較器の過負荷が必要ですか? 常にデフォルトにフォールバックできますか? (免責事項:この場合、知識/専門知識が不足しているので、質問してください)
これは、2つの理由から非常に重要だと思います。
IComparable
を実装するクラスを常に提供するようにユーザーに強制します。さらに、既存のAPIと一貫性があります。 SortedDictionaryをご覧ください。
keySelectorのオーバーロードが必要ですか? 価値の一部として優先するか、それとも別のものにするかを決める必要があると思います。 個別の優先順位は私にはもう少し自然に思えますが、私は強い意見を持っていません。 私たちは賛否両論を知っていますか?
私も別の優先順位が好きです。 それとは別に、これを実装する方が簡単で、パフォーマンスとメモリ使用量が向上し、APIがより直感的になり、ユーザーの作業が少なくなります( IComparable
を実装する必要はありません)。
今セレクターについて...
これが、この優先キューを柔軟にする理由です。 これにより、ユーザーは次のことが可能になります。
IComparable
を実装する要素それは私が考えることができるほとんどすべての構成を可能にします。 ユーザーは「プラグアンドプレイ」するだけなので、便利だと思います。 また、かなり直感的です。
Enqueue
およびUpdate
メソッド。クラス名
PriorityQueue
対Heap
IHeap
とコンストラクターのオーバーロードを導入します。
Heap
ではなく、 PriorityQueue
をCoreFXの一部にする必要があると感じています。IHeap
インターフェイスに関して—優先度付きキューはヒープとは異なるもので実装できるため、おそらくこの方法で公開したくないでしょう。 ただし、 IPriorityQueue
が必要になる場合があります。インターフェイスを出荷すれば、ほぼ完了です。 そのAPIを反復する余地はあまりないので、最初は正しいことを確認し、今後数年間は正しいことを継続します。 いくつかの便利な機能が欠けていることは、すべてをより良くするこの1つの小さな変更だけを行うのが非常に良い、潜在的に不十分なインターフェイスで立ち往生するよりもはるかに良い場所です。
完全に同意する!
別の仮定を追加すると、要素は比較可能でなければならないという仮定があれば、APIはさらに単純になります。 しかし、繰り返しになりますが、柔軟性は低くなります。
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にも同意します。新しいクラスのAPIが使用されるまで待機し、実際にインターフェースを出荷してからフィードバックを受け取る必要があります。そのバージョンがストックされており、すでに使用している顧客を壊さずに変更することはできません。
比較者についても、他のBCL APIに準拠していると思います。また、ユーザーが新しいラッパークラスを提供する必要があるという事実は気に入らないと思います。 コンストラクターのオーバーロードが比較子アプローチを受け取り、内部で実行する必要があるすべての比較の中でその比較子を使用するのが本当に好きです。比較子が提供されていないか、比較子がnullの場合は、デフォルトの比較子を使用します。
@pgolebiowskiこのような詳細で説明的なAPI提案に感謝し、このAPIが承認されてCoreFXに追加されるように積極的かつ精力的に取り組んでいます👍このディスカッションが完了し、レビューの準備ができていると考えた場合、すべての入力と最終的なAPIサーフェスをマージします1つのコメントにまとめ、上部の主要な問題のコメントを更新します。これにより、レビュー担当者の作業が楽になります。
OK、私は比較者について確信しています、それは理にかなっていて、それは一貫しています。
私はまだセレクターに引き裂かれています-IMOはそれなしで行こうとすべきです-それを2つのバリアントに分割しましょう。
`` `c#
パブリッククラスPriorityQueue
:IEnumerable、
IEnumerable <(TElement要素、TPriority優先度)>、
IReadOnlyCollection <(TElement要素、TPriority優先度)>
// ICollectionは意図的に含まれていません
{{
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
public Func<TElement, TPriority> PrioritySelector { get; }
public void Enqueue(TElement element); // O(log n)
public void Update(TElement element); // O(log n)
}
「」
未解決の質問:
PriorityQueue
対Heap
IHeap
とコンストラクターのオーバーロードを導入しますか? (後で待つ必要がありますか?)IPriorityQueue
紹介しますか? (後で待つ必要があります- IDictionary
例)(TElement element, TPriority priority)
とKeyValuePair<TPriority, TElement>
Peek
とDequeue
は、タプルではなくout
引数を指定する必要がありますか?早めのフィードバックのために、明日APIレビューグループで実行してみます。
Peek
とDequeue
タプルフィールド名をpriority
修正しました(指摘してくれた
上記の最新の提案で更新されたトップポスト。
私は@safernのコメントを2番目にしてい@pgolebiowskiに感謝します!
未解決の質問:
ここにもう1つあると思います。
良い点は、未解決の質問ではなく、トップの投稿で「仮定」として捉えられています。 反対に、APIはかなり醜いものになります。
我々は返す必要がありますbool
からRemove
? また、 out priority
argとしての優先順位は? (最近、他のデータ構造に同様のオーバーロードを追加したと思います)
Contains
似ています-誰かがそれから優先順位を望んでいるに違いありません。 out priority
オーバーロードを追加したい場合があります。
ヒープは実装を意味し、これをPriorityQueue
と呼ぶことを正当にサポートするだろうという意見は残っています(まだ声を上げていませんが)。 (その場合、重複要素を許可し、更新などを許可しない、よりスリムなHeap
クラスの提案もサポートします(元の提案に沿ったものです)が、それは期待していません。発生する)。
KeyValuePair<TPriority, TElement>
は、重複する優先順位が予想されるため、絶対に使用しないでください。また、 KeyValuePair<TElement, TPriority>
も混乱を招くと思うので、 KeyValuePair
をまったく使用しないことをサポートします。プレーンタプル、または優先度のoutパラメーターを使用します(個人的にはoutパラメーターが好きですが、大騒ぎしていません)。
重複を許可しない場合は、異なる/変更された優先順位でそれらを再追加しようとする動作を決定する必要があります。
私はセレクターの提案を支持しません。それは冗長性を意味するという単純な理由で、そして正当に混乱を再現するからです。 要素の優先度が一緒に保存されている場合、それは2つの場所に保存され、それらが同期しなくなった場合(つまり、誰かが役に立たないように見えるUpdate(TElement)
メソッドを呼び出すのを忘れた場合)、多くの苦しみがあります確実になります。 セレクターがコンピューターの場合、意図的に要素を追加し、計算元の値を変更する人を受け入れます。今、彼らがそれを再度追加しようとすると、状況によってはうまくいかない可能性のあることがたくさんあります。これが発生したときに何が起こるかの決定について。 少し高いレベルでは、要素の変更されたコピーが追加される可能性があります。これは、以前と同じではなくなったためです(これは可変キーの一般的な問題ですが、優先度と要素を分離すると、潜在的な問題を回避するのに役立ちます)。
セレクター自体が動作を変更する傾向があります。これは、ユーザーが考えずにすべてを壊すことができるもう1つの方法です。 ユーザーに優先順位を明示的に提供させる方がはるかに良いと思います。 これで私が目にする大きな問題は、要素ではなくペアを宣言しているため、重複エントリが許可されていることを伝えていることです。 ただし、賢明なインラインドキュメントとEnqueue
bool
戻り値は、これを簡単に修正する必要があります。 ブール値よりも優れているのは、要素の古い/新しい優先度を返すことです(たとえば、 Enqueue
が新しく提供された優先度、2つのうちの最小値、または古い優先度を使用する場合)が、 Enqueue
は、何かを再度追加しようとすると失敗するはずなので、成功を示すbool
を返すだけです。 これにより、 Enqueue
とUpdate
完全に分離され、明確に定義されます。
KeyValuePairをまったく使用せず、プレーンタプルを使用することもサポートします
私はタプルであなたと一緒です。
重複を許可しない場合は、異なる/変更された優先順位でそれらを再追加しようとする動作を決定する必要があります。
Dictionary
内のインデクサーとの類似性がわかります。 そこで、 dictionary["something"] = 5
を実行すると、 "something"
が以前にキーであった場合に更新されます。 そこになかった場合は、追加されるだけです。Enqueue
メソッドは、辞書のAdd
メソッドに似ています。 つまり、例外をスローする必要があります。Enqueue
メソッドは例外をスローするだけでよいという結論につながります。 同様に、存在しない要素の優先度を誰かが更新したい場合、 Update
メソッドは例外をスローする必要があります。bool
返すTryUpdate
メソッドを追加します。冗長性を意味するという単純な理由で、セレクターの提案はサポートしていません
キーが物理的にコピーされていない(存在する場合)のではなく、セレクターは、優先順位を評価する必要があるときに呼び出される関数のままです。 冗長性はどこにありますか?
優先度と要素を分離すると、潜在的な問題を回避するのに役立つと思います
唯一の問題は、顧客が物理的な優先順位を持っていない場合です。 その時彼は多くをすることができません。
ユーザーに優先順位を明示的に提供させる方がはるかに良いと思います。 これで私が目にする大きな問題は、要素ではなくペアを宣言しているため、重複エントリが許可されていることを伝えていることです。
そのソリューションにはいくつかの問題がありますが、重複エントリが許可されていることを伝える理由は必ずしもありません。 「重複なし」ロジックはTElement
のみ適用する必要があると思います。優先順位はここでは単なる値です。
var queue = new PriorityQueue<string, double>();
queue.Enqueue("first", 0.1);
queue.Enqueue("first", 0.5); // should be unsuccessful IMO
@VisualMelonは意味がありますか?
@pgolebiowski
私はTryUpdate
を追加することに完全に同意し、 Update
投げることは私には理にかなっています。 私は( IDictionary
ではなく) Enqueue
に関してISet
方針に沿ってもっと考えていました。 投げることも理にかなっているでしょう、私はその点を逃したに違いありません、しかし私はリターンbool
がタイプの「セットネス」を伝えると思います。 おそらくTryEnqueue
も( Add
投げて)順番になっているでしょうか? TryRemove
もありがたいです(空の場合は失敗します)。 最後のポイントですが、そうです、それも私が考えていた行動です。 IDictionary
とのアナロジーは、リフレクションではISet
よりも優れていると思いますが、それは十分に明確であるはずです。 (要約すると、私はあなたの提案に従って投げるすべてをサポートしますが、その場合はTry*
を持っている必要があります。失敗条件に関するあなたの声明にも同意します)。
インデクサーに関しては、キューの概念に実際には「適合」していないのは正しいと思います。私はそれをサポートしません。 どちらかといえば、キューまたは更新するための適切な名前のメソッドが適切です。
キーが物理的にコピーされていない(存在する場合)のではなく、セレクターは、優先順位を評価する必要があるときに呼び出される関数のままです。 冗長性はどこにありますか?
そうです、私は提案の更新を読み間違えました(それを要素に保存することについてのビット)。 セレクターがオンデマンドで呼び出されるとすると(パフォーマンスに影響を与える可能性があるため、ドキュメントで明確にする必要があります)、関数の結果はデータ構造が応答せずに変更される可能性があり、 Update
が呼び出されない限り、2つの非同期。 さらに悪いことに、ユーザーが複数の要素の有効な優先度を変更し、そのうちの1つだけを更新した場合、データ構造は、「更新されていない」要素から選択されたときに「コミットされていない」変更に依存することになります(私は調べていません)提案されたDataStructureの実装の詳細ですが、これはすべてのUpdate O(<n)
にとって必然的に問題になると思います)。 ユーザーに優先順位を明示的に更新するように強制すると、これが改善され、データ構造が一貫した状態から別の状態に移行する必要があります。
セレクターの提案についてこれまで述べてきたすべての不満は、APIの堅牢性に関するものであることに注意してください。セレクターを使用すると、誤った使用が簡単になると思います。 ただし、使いやすさはさておき、概念的には、キュー内の要素はそれらの優先順位を知る必要はありません。 それは潜在的に彼らとは無関係であり、ユーザーが要素をstruct Queable<T>{}
か何かでラップすることになった場合、それは摩擦のないAPIを提供することに失敗したように見えます(私がこの用語を使用するのは痛いです)。
もちろん、セレクターがないと、ユーザーがセレクターを呼び出す負担があると主張することもできますが、要素が優先順位を知っていれば、(うまくいけば)きちんと公開され(つまりプロパティ)、そうでない場合はそうなると思います。 tの場合、心配するセレクターはありません(オンザフライで優先度を生成するなど)。 セレクターが必要ない場合は、セレクターをサポートするために必要な、さらに多くの無意味な配管があります。明確に定義された「セレクター」がある場合は、優先順位を渡します。 セレクターは、この情報を公開するようにユーザーを誘導しますが(おそらく役に立たない)、個別の優先順位は、設計上の決定に影響を与えるとは想像できない非常に透過的なインターフェイスを提供します。
セレクターを支持して私が本当に考えることができる唯一の議論は、それはあなたがPriorityQueue
渡すことを使用できることを意味し、プログラムの他の部分は優先順位がどのように計算されるかを知らなくてもそれを使用できるということです。 これについてはもう少し考えなければなりませんが、「ニッチ」な適合性を考えると、より一般的なケースでは、かなり重いオーバーヘッドに対する報酬はほとんどありません。
_編集:もう少し詳しく説明しますが、Elementsを投げることができる自己完結型のPriorityQueue
があると非常に便利ですが、これを回避するためのコストは大きいと思いますが、導入のコストはかなり少なくなります。_
私は、オープンソースの.NETコードベースを調べて、優先度付きキューがどのように使用されているかの実際の例を確認するために少し作業を行ってきました。 トリッキーな問題は、学生のプロジェクトとトレーニングのソースコードを除外することです。
使用法1:Roslyn通知サービス
https://github.com/dotnet/roslyn/
Roslynコンパイラには、「PriorityQueue」と呼ばれるプライベート優先度キューの実装が含まれています。これは、非常に特殊な最適化が行われているようです。オブジェクトキューは、キュー内のオブジェクトを再利用して、ガベージコレクションを回避します。 Enqueue_NoLockメソッドは、current.Value.MinimumRunPointInMS <entry.Value.MinimumRunPointInMSで評価を実行して、キュー内のどこに新しいノードを配置するかを決定します。 ここで提案されている2つの主要な優先度キューの設計(比較関数/デリゲートと明示的な優先度)のいずれかが、この使用シナリオに適合します。
使用法2:Lucene.net
https://github.com/apache/lucenenet
これは間違いなく、.NETで見つけたPriorityQueueの最大の使用例です。 Apache Lucene.netは、人気のあるLucene検索エンジンライブラリの完全な.netポートです。 私の会社ではJavaバージョンを使用しており、ApacheのWebサイトによると、いくつかの有名人が.NETバージョンを使用しています。 Githubには.NETプロジェクトのフォークが多数あります。
Luceneには、HitQueue、TopOrdAndFloatQueue、PhraseQueue、SuggestWordQueueなどのいくつかの「特殊な」優先度キューによってサブクラス化された独自のPriorityQueue実装が含まれています。 同様に、プロジェクトは多くの場所でPriorityQueueを直接インスタンス化します。
上記にリンクされているLuceneのPriorityQueueの実装は、この号で投稿された元の優先度キューAPIと非常によく似ています。 「PriorityQueue」として定義されています
LuceneのPriorityQueueの優先度は、コンストラクターに渡された比較子によって決定され、何も渡されなかった場合は、比較されたオブジェクトがIComparableを実装していると見なされます。
コードベースには多数の使用例があり、 SloppyPhraseScorerもその1つです。
SloppyPhraseScorerのコンストラクターは、PriorityQueueの独自のカスタムサブクラスの1つである新しいPhraseQueue(pq)をインスタンス化します。 PhrasePositionsのコレクションが生成されます。これは、一連の投稿、位置、および一連の用語のラッパーのように見えます。 FillQueueメソッドは、フレーズの位置を列挙してキューに入れます。 PharseFreq()はAdvancePP関数を呼び出し、高レベルでは、デキューし、アイテムの優先度を更新してから、再度エンキューするように見えます。 優先度は、明示的ではなく(比較子を使用して)相対的に決定されます(優先度は、エンキュー中に2番目のパラメーターとして「渡される」ことはありません)。
PhraseQueueの実装に基づいて、コンストラクターを介して渡された比較値(整数など)がそれをカットしない可能性があることがわかります。 それらの比較関数( "LessThan")は、PhrasePositions.doc、PhrasePositions.position、およびPhrasePositions.offsetの3つの異なるフィールドを評価します。
使用法3:ゲーム開発
このスペースでの使用例の検索はまだ完了していませんが、ゲーム開発で使用されているカスタム.NETPriorityQueueの例をかなり多く見ました。 非常に一般的な意味で、これらは主なユースケース(ダイクストラ法)としてパスファインディングを中心にクラスター化される傾向がありました。 Unity 3Dにより、.NETでパスファインディングアルゴリズムを実装する方法を尋ねる人がたくさんいます。
まだこの領域を掘り下げる必要があります。 明示的な優先順位がキューに入れられているいくつかの例と、Comparer / IComparableを使用しているいくつかの例を見ました。
別の注意点として、一意の要素、明示的な要素の削除、および特定の要素が存在するかどうかの判断について、いくつかの議論がありました。
キューは、データ構造として、通常、エンキューとデキューをサポートします。 他のセット/リストのような操作を提供する道を進むと、実際にはまったく別のデータ構造を設計しているのではないかと思います。これは、タプルのソートされたリストに似ています。 発信者がエンキュー、デキュー、ピーク以外のニーズを持っている場合、おそらく優先キュー以外のものが必要ですか? キューは、定義上、キューへの挿入とキューからの順序付き削除を意味します。 他にはあまりありません。
@ebickle
他のリポジトリを調べて、優先キュー機能がどのように提供されているかを確認していただき、ありがとうございます。 ただし、IMOのこの議論は、特定の設計提案を提供することでメリットがあります。 このスレッドはすでにフォローするのが難しく、結論なしに長い話をすることはそれをさらに難しくします。
キュー[...]は通常、エンキューとデキューをサポートします。 [...]発信者がエンキュー、デキュー、ピーク以外のニーズを持っている場合、おそらく優先キュー以外のものが必要ですか? キューは、定義上、キューへの挿入とキューからの順序付き削除を意味します。 他にはあまりありません。
素晴らしい議論。 @ebickleの調査は非常に便利なIMOです!
@ebickle [2] lucene.netについて結論はありますか?最新の提案は使用法に適合していますか? (私はあなたの詳細な説明でそれを見逃していなかったと思います)
上からTry*
バリアントが必要なようです+ IsEmpty
+ TryPeek
/ TryDequeue
+ EnqueueOrUpdate
? 考え?
`` `c#
public bool IsEmpty();
public bool TryPeek(ref TElement element, ref TPriority priority); // false if empty
public bool TryDequeue(ref TElement element, ref TPriority priority); // false if empty
public bool TryEnqueue(TElement element, TPriority priority); // false if it is duplicate (doe NOT update it)
public void EnqueueOrUpdate(TElement element, TPriority priority); // TODO: Should return bool status for enqueued vs. updated?
public bool TryUpdate(TElement element, TPriority priority); // false if element does not exist (does NOT add it)
public bool TryRemove(TElement element); // false if element does not exist
`` `
@karelz
上からTry *バリアントが必要なようです
はい、正確に。
キューに入れられたものと更新されたもののブールステータスを返す必要がありますか?
どこでもステータスを返したい場合は、どこでも。 この特定の方法では、いずれかの操作( Enqueue
またはUpdate
)が成功した場合に当てはまるはずです。
残りの部分については-私は単に同意します:smile:
たった1つの質問-なぜref
代わりにout
か? わからない:
どこでもステータスを返したい場合は、どこでも。 この特定の方法では、いずれかの操作(
Enqueue
またはUpdate
)が成功した場合に当てはまるはずです。
それは常にtrueを返しますが、それは良い考えではありません。 このような場合、IMOはvoid
を使用する必要があります。 そうしないと、人々は混乱し、戻り値をチェックして、実行されない無駄なコードを追加します。 (私が何かを逃した場合を除いて)
ref vs. outが同意し、私はそれについて自分で議論しました。 自分で決断するほどの強い意見や経験がありません。 APIレビューアに質問するか、コメントが増えるのを待つことができます。
それは常にtrueを返します
あなたは正しい、私の悪い。 ごめん。
自分で決断するほどの強い意見や経験がありません。
何かが足りないかもしれませんが、それはとても簡単だと思います。 ref
を使用する場合、基本的に、 Peek
とDequeue
は、渡されたTElement
とTPriority
を何らかの方法で使用したいと言っています(私はそれらのフィールドを読むことを意味します)。 実際にはそうではありません-私たちのメソッドはそれらの変数にのみ値を割り当てることになっています(そして実際にはコンパイラーによってそうする必要があります)。
Try*
APIで更新されたトップポスト
2つの未解決の質問を追加しました:
Peek
とDequeue
スローはまったく役に立ちますか?TryPeek
およびTryDequeue
- ref
またはout
引数を使用する必要がありますか?ref vs.out-その通りです。 falseを返した場合に備えて、初期化を回避するために最適化していました。 それは私から愚かで盲目的でした-時期尚早の最適化。 outに変更して、質問を削除します。
6:何かが足りないかもしれませんが、例外をスローしない場合、キューが空の場合、 Peek
またはDequeue
はどうすればよいですか? これは、データ構造がnull
を受け入れるべきかどうかについて疑問を投げかけ始めていると思います(私は望まないが、確固たる意見はありません)。 null
を許可しても、値型にはnull
がないため( default
確かにカウントされません)、 Peek
とDequeue
は意味のない結果を伝える方法はありません。当然、例外をスローする必要があると思います(したがって、 out
パラメーターに関する懸念を取り除きます!)。 既存のQueue.Dequeue
の例に従わない理由はわかりません
ダイクストラ(別名、ヒューリスティックなしのヒューリスティック検索)では、優先度を変更する必要がないことを付け加えておきます。 私は何かの優先度を更新したくなかったので、ヒューリスティック検索を常に打ち消します。 (アルゴリズムのポイントは、状態を探索すると、その状態への最適なルートを探索したことがわかっていることです。そうしないと、最適ではなくなるリスクがあるため、優先順位を改善することはできず、確かに改善しません。それを減らしたい(つまり、より悪いルートを検討する)。_ヒューリスティックを意図的に選択して最適ではない場合を無視します。この場合、もちろん最適ではない結果が得られますが、それでも決して最適ではありません。優先度を更新します_)
例外をスローしない場合、キューが空の場合、PeekまたはDequeueは何をする必要がありますか?
NS。
したがって、アウトパラメータに関する懸念を取り除くことができます!
どのように? ええと、 out
パラメータはTryPeek
とTryDequeue
メソッド用です。 例外をスローするのはPeek
とDequeue
です。
ダイクストラは優先順位の変更を必要としないはずだと付け加えておきます。
私は間違っているかもしれませんが、私の知る限り、ダイクストラのアルゴリズムはDecreaseKey
演算を利用しています。 たとえば、これを参照してください。 これが効率的かどうかは別の側面です。 実際、フィボナッチヒープは、O(1)で漸近的にDecreaseKey
操作を実現するように設計されています(ダイクストラを改善するため)。
しかし、それでも、私たちの議論で重要なことは、優先度付きキューの要素を更新できることは非常に役立ち、そのような機能を検索する人々がいます(StackOverflowで以前にリンクされた質問を参照してください)。 私も数回使ったことがあります。
申し訳ありませんが、はい、 out
パラメータの問題が発生し、もう一度読み間違えています。 そして、ダイクストラの変種(私が信じていたよりもかなり広く適用されているように見える名前...)は、おそらくより効率的に実装できるようです(キューにすでに要素が含まれている場合は、繰り返し検索できるという利点があります)。更新可能な優先順位。 それは私が長い単語と名前を使うことで得られるものです。 Update
を削除することを提案しているわけではないことに注意してください。この機能が課す制限なしに、よりスリムなHeap
またはその他(つまり元の提案)を用意するのもよいでしょう(栄光のように)私が感謝している機能です)。
7:これらはout
必要があります。 どの入力も意味を持たない可能性があるため、存在してはなりません(つまり、 ref
を使用するべきではありません)。 out
パラメータでは、 default
値を返すことを改善するつもりはありません。 これはDictionary.TryGetValue
が行うことであり、それ以外の理由はありません。 _そうは言っても、 ref
を値またはデフォルトとして扱うことはできますが、意味のあるデフォルトがない場合は、問題を苛立たせます。_
APIレビューディスカッション:
重要な生のメモは次のとおりです。
ImmutableCollections
についても同様のことを行いました(高速プレビューリリースサイクルが重要であり、APIの形成に役立ちました)。MultiValueDictionary
とバンドルできます。 TODO:候補者がもっといるかどうかを確認してください。それぞれを個別にブログに投稿したくありません。Peek
とDequeue
TElement
からPeek
だけを返し、 TPriority
は返さないでください(ユーザーはTry*
メソッドを使用できます)。Update
削除します-ユーザーはRemove
を削除してから、異なる優先度でEnqueue
アイテムを元に戻すことができます。Remove
は、最初に見つかったアイテムのみを削除する必要があります( List
同様)。IQueue
インターフェースを抽象化してQueue
とPriorityQueue
を抽象化できますか( Peek
とDequeue
TElement
だけを返しますここ)私たちはセレクターについて長い間議論しました-私たちはまだ決定されていません(上記のIQueue
によって要求されるかもしれませんか?)。 大多数はセレクターが気に入らなかったが、将来のAPIレビュー会議で状況が変わる可能性がある。
他の未解決の質問は議論されませんでした。
最新の提案は私にはかなり良さそうです。 私の意見/質問:
Peek
とDequeue
がpriority
にout
パラメータを使用した場合、優先度をまったく返さないオーバーロードが発生する可能性もあります。これは単純化すると思います。一般的な使用法。 ただし、破棄を使用して優先度を無視することもできるため、これはそれほど重要ではありません。PriorityQueue<T>
があるはずですか? または、 PriorityQueue<T, T>
機能する静的メソッドと拡張メソッドのセット?priorityQueue.Select(t => t.element)
ようなものを使用する必要がありますか?Dictionary
している場合、 IEqualityComparer<TElement>
を渡すオプションが必要ですか?Dictionary
を使用して要素を追跡することは、優先順位を更新する必要がない場合、不要なオーバーヘッドです。 それを無効にするオプションがあるべきですか? これは後で追加できますが、便利であることが判明した場合。@karelz
安定性(同じ優先度の場合)-同じ優先度のアイテムは、キューに挿入された順序で返される必要があります(一般的なキューの動作)
これは、内部的には、優先度が(priority, version)
ペアのようなものである必要があることを意味すると思います。ここで、 version
は追加ごとに増分されます。 特にversion
が64ビットである必要があることを考えると、これは優先キューのメモリ使用量を大幅に膨らませると思います。 それが価値があるかどうかはわかりません。
重複する値を防止したくない(他のコレクションと同様):
Update
削除します-ユーザーはRemove
を削除してから、異なる優先度でEnqueue
アイテムを元に戻すことができます。
実装によっては、 Update
は、 Remove
後にEnqueue
続くよりもはるかに効率的である可能性があります。 たとえば、バイナリヒープ(クォータナリヒープは同じ時間計算量を持っていると思います)の場合、 Update
(一意の値とディクショナリを使用)はO(log n )ですが、 Remove
(重複した値を使用)辞書はありません)はO( n )です。
@pgolebiowski
ここでの提案に集中し続ける必要があることに完全に同意します。 オリジナルのAPI提案とオリジナルの投稿を行いました。 1月に、 @ karelzはいくつかの具体的な使用例を求めました。 これにより、PriorityQueueAPIの特定の必要性と使用法に関するより広範な質問が開かれました。 私は独自のPriorityQueue実装を持っていて、それをいくつかのプロジェクトで使用し、同様の何かがBCLで役立つと感じました。
元の投稿に欠けていたのは、既存のキューの使用状況に関する広範な調査でした。 実際の例は、設計を固定し、広く使用できるようにするのに役立ちます。
@karelz
Lucene.netには、少なくとも1つの優先度キュー比較関数が含まれています(比較と同様)
提案:
1)PriorityQueue
2)PriorityQueue
どちらのタイプも重複要素をサポートしています。 同じ優先度の要素に対してFIFOを保証するかどうかを決定する必要があります。 優先度の更新をサポートしている場合は、おそらくそうではありません。
@karelzが提案したように、CoreFxLabsで両方のバリアントを
質問:
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);
投げる必要があります。 しかし、なぜTryバリアントや拡張メソッドがないのですか?
構造体ベースの列挙子?
@benaadamsは、醜いハンドルタイプを避けるために、元々(私が)提案しました。 APIレビューからの最新の更新はそれを元に戻します。
タイプにメソッドを追加できる場合は、拡張メソッドとしてメソッドを追加しないでください。 拡張メソッドは、型に追加できない場合(たとえば、インターフェイスである場合、または.NET Frameworkに高速に配信したい場合)のバックアップです。
重複:同じエントリが複数ある場合は、1つだけ更新する必要がありますか? それから彼らは違うのですか?
投げ方:基本的にはラッパーであり、そうなるのでしょうか?
public void Remove(TElement element)
{
if (!TryRemove(element))
{
throw new Exception();
}
}
その制御は例外を介して流れますが?
@benaadamsはAPIレビューノートで私の返信をチェックします: https :
Update
削除します-ユーザーはRemove
から、異なる優先度でEnqueue
アイテムを元に戻すことができます。Remove
は、最初に見つかったアイテムのみを削除する必要があります( List
同様)。その制御は例外を介して流れますが?
よく分からない。 BCLではかなり一般的なパターンのようです。
その制御は例外を介して流れますが?
よく分からない。
TryXメソッドがある場合
例外の関与はありません。 しかし、その名前はあなたがリターンを捨てる決定をすることを奨励します。
試行しない例外スローメソッドがある場合
したがって、フロー制御に例外処理を使用するというアンチパターンになります。 それらは少し不必要に思えます。 falseの場合は、いつでもスローを追加して再作成できます。
BCLではかなり一般的なパターンのようです。
TryXメソッドは、元のBCLでは一般的なパターンではありませんでした。 並行型まで(2.0のDictionary.TryGetValue
が最初の例だったと思いますか?)
たとえば、TryXメソッドはキューとスタックのコアに追加されたばかりで、まだフレームワークの一部ではありません。
IComparer
を公開します。 お客様が安定性を求めている場合は、簡単に自分で追加できます。 要素の「年齢」を保存し、比較中にそれを使用する通常のアプローチを使用して、実装の上にStablePriorityQueue
を構築するのその場合、APIの競合が発生します。 ここで、既存のQueue<T>
で機能するように、単純なIQueue<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);
}
優先キューには2つのオプションがあります。
IQueue<TElement>
実装します。IQueue<(TElement, TPriority)>
実装します。単純な数字(1)と(2)を使用して、両方のパスの意味を記述します。
優先度付きキューでは、要素とその優先度の両方をキューに入れる必要があります(現在のソリューション)。 通常のヒープでは、要素を1つだけ追加します。
Enqueue(TElement)
を公開します。これは、優先度なしで要素を挿入する場合、優先度付きキューではかなり奇妙です。 それから私達はすることを余儀なくされます...何ですか? default(TPriority)
と仮定しますか? いや...Enqueue((TElement, TPriority) element)
を公開します。 基本的に、それらから作成されたタプルを受け入れることにより、2つの引数を追加します。 おそらく理想的なAPIではありません。これらのメソッドがTElement
のみを返すようにしたかったのです。
(TElement, TPriority)
返します。PriorityQueue<T>
提供することで、いくつかの問題を軽減できます。ここでは、物理的に個別の優先順位はありません。
IComparable
も実装したいということを意味します。 これにより、かなり定型的なコードがたくさん追加されます(そして、おそらくソースコードに新しいファイルが追加されます)。2つの優先キューを提供すると、ソリューション全体がより強力で柔軟になります。 取るべきいくつかの注意事項があります:
PriorityQueue<T>
は、 IQueue<T>
実装する可能性があります。PriorityQueue<TElement, TPriority>
は、 IQueue
インターフェースを実装しようとすると、奇妙なAPIを公開します。まあ…それはうまくいく
私たちは重複を許可することを前提として考えると、私たちは、ハンドルの概念を使用したくありません。
これは基本的に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
を使用することで、シンプルなAPIと簡単な使用法を利用できます。コードを正しく機能させたい人向けです。PriorityQueue
安定させることができます。これは、もはやトレードオフではありません。Heap
存在を認識しているという感覚と調和しています。 また、 PriorityQueue
の制限を認識しているため、代わりにHeap
使用できます(たとえば、要素の更新/削除をより細かく制御したい場合や、データが必要ない場合など)速度とメモリ使用量を犠牲にして安定する構造)。IQueue
インターフェースを作成する方が簡単です。これは、パワーと機能がヒープフィールドに投入されるためです。 PriorityQueue
のAPIは、抽象化によってQueue
と互換性を持たせることに焦点を当てることができます。IPriorityQueue
インターフェイスを提供する必要はもうありません( PriorityQueue
とQueue
同じように保つことに重点を置いています)。 代わりに、ヒープ領域に追加できます。基本的に、そこにIHeap
があります。 これは、標準ライブラリにあるものの上にサードパーティのライブラリを構築できるので素晴らしいです。 そして、それは正しいと感じます-繰り返しになりますが、優先キューよりも高度であると考えているため、拡張機能はその領域によって提供されます。 このような拡張機能は、個別であるため、 PriorityQueue
で行う選択の影響も受けません。PriorityQueue
のIHeap
コンストラクターを考慮する必要PriorityQueue
もうありません。Heap
があります!基本的に、優先キューは、パフォーマンス、パワー、および柔軟性を犠牲にして、主に使いやすさに焦点を合わせます。 ヒープは、優先度付きキューでの決定のパフォーマンスと機能への影響を認識している人向けです。 トレードオフに関する多くの問題を軽減します。
実験をするなら、今は可能だと思います。 コミュニティに聞いてみましょう。 誰もがこのスレッドを読むわけではありません。他の貴重なコメントや使用シナリオを生成する可能性があります。 どう思いますか? 個人的に、私はそのような解決策が大好きです。 みんなが幸せになると思います。
重要な注意:このようなアプローチが必要な場合は、優先キューとヒープを一緒に設計する必要があります。 それらの目的は異なり、一方のソリューションはもう一方のソリューションでは提供されないものを提供するためです。
上記のアプローチでは、優先度付きキューがIQueue<T>
を実装するために(意味があるように)、セレクターのサポートをそこで削除すると仮定すると、1つの汎用タイプが必要になります。 それは、ユーザーが(user data, priority)
別々に持っている場合、ラッパーを提供する必要があることを意味しますが、そのようなソリューションは依然として直感的です。 そして最も重要なことは、すべての入力形式を許可することです(そのため、セレクターをドロップした場合は、この方法で行う必要があります)。 セレクターがないと、 Enqueue(TElement, TPriority)
はすでに比較可能なタイプを許可しません。 このメソッドをIQueue<T>
含めることができるように、単一のジェネリック型も列挙に不可欠です。
@svick
列挙時に返される要素はどのような順序で定義されていますか? 私の推測では、それを効率的にするために、それは未定義です。
列挙しながら順序を設定するには、基本的にコレクションを並べ替える必要があります。 そういうわけで、はい、それは未定義でなければなりません。顧客は自分でOrderBy
実行して、ほぼ同じパフォーマンスを達成できるからです(ただし、それを必要としない人もいます)。
アイデア:優先キューでは、これは順序付けられ、ヒープでは順序付けられません。 気分が良くなります。 どういうわけか、優先キューは順番に繰り返すように感じます。 ヒープは絶対にありません。 上記のアプローチのもう1つの利点。
@pgolebiowski
それはすべて非常に正気のようです。 Delivering IQueue then
セクションで、重複要素が許可されているセレクターの代わりに、「1つのジェネリック型」にT : IComparable<T>
を提案していることを明確にしていただけませんか。
私は2つの別々のタイプを持つことをサポートします。
ハンドルタイプとしてobject
を使用する理由がわかりません。これは、新しいタイプの作成を回避するためだけですか? 新しいタイプを定義すると、実装のオーバーヘッドが最小限に抑えられ、APIの誤用が難しくなり( string
をRemove(object)
に渡そうとするのを妨げているのは何ですか?)、使いやすくなります(私が試しているのを止めているのは何ですか?) Element自体をRemove(object)
に渡すために、そして誰が私を試みたと非難することができますか?)
より表現力豊かなインターフェースのために、handleメソッドのobject
を置き換えるために、適切な名前のダミータイプを追加することを提案します。
デバッグ可能性がメモリオーバーヘッドよりも優先される場合、ハンドルタイプには、それが属するキューに関する情報を含めることもできます(Genericになる必要があります。これにより、インターフェイスのタイプの安全性が向上します)。別のキューによって」)、またはそれがすでに消費されているかどうか(「ハンドルによって参照される要素はすでに削除されています」)。
ハンドルのアイデアが進んだ場合、この情報が有用であると見なされた場合、これらの例外のサブセットも、対応するTryRemove
およびTryUpdate
メソッドによってスローされることを提案します。ただし、要素がデキューされたか、ハンドルによって削除されたため、要素は存在しなくなりました。 これには、退屈ではなく、一般的で、適切な名前のハンドルタイプが必要になります。
@VisualMelon
Delivering IQueue then
セクションで、重複要素が許可されているセレクターの代わりに、「1つのジェネリック型」にT : IComparable<T>
を提案していることを明確にしていただけませんか。
これを明確にしないことをお詫びします。
T
制約がなく、 PriorityQueue<T>
を配信することを意味しました。IComparer<T>
ます。T
がすでに比較可能である場合は、単にComparer<T>.Default
が想定されます(引数なしで優先度付きキューのデフォルトコンストラクターを呼び出すことができます)。セレクターの目的は異なります。つまり、すべてのタイプのユーザーデータを使用できるようにすることです。 いくつかの構成があります。
まれなケースはPriorityQueue<T>
では不可能ですが、それはそれほど重要ではありません。 重要なのは、(1)、(2)、(3)を処理できるようになったことです。 ただし、2つのジェネリック型がある場合は、 Enqueue(TElement, TPrioriity)
ようなメソッドが必要になります。 それは私たちを(1)だけに制限するでしょう。 (2)冗長性につながります。 (3)信じられないほど醜いでしょう。 これについては、上記のIQueue > Enqueue
セクション(2番目のEnqueue
メソッドとdefault(TPriority
)にもう少しあります。
今はもっとはっきりしているといいのですが。
ところで、そのようなソリューションを想定すると、 PriorityQueue<T>
とIQueue<T>
のAPIを設計するのは簡単です。 Queue<T>
いくつかのメソッドを取得し、それらをIQueue<T>
にスローして、 PriorityQueue<T>
実装させます。 タダー! 😄
ハンドルタイプとして
object
を使用する理由がわかりません。これは、新しいタイプの作成を回避するためだけですか?
PriorityQueue<T>
をSystem.Collections.Generic
に常駐させ、ヒープ機能をSystem.Collections.Specialized
ます。 そこでは、そのようなタイプを導入する可能性が高くなり、最終的には素晴らしいコンパイル時エラー検出が可能になります。デバッグ可能性がメモリオーバーヘッドよりも優先される場合、ハンドルタイプには、それが属するキューに関する情報を含めることもできます(Genericになる必要があります。これにより、インターフェイスのタイプの安全性が向上します)。別のキューによって」)、またはそれがすでに消費されているかどうか(「ハンドルによって参照される要素はすでに削除されています」)。
ハンドルのアイデアが進んだら、この情報が役立つと思われる場合は提案します...
間違いなくもっと可能です:ウィンク:。 特に、サードパーティのライブラリで私たちのコミュニティによってそれらすべてを拡張するために。
2つのクラスを作成するというアイデアは非常に理にかなっており、多くの問題を解決すると思います。 私が持っているのは、 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
。IQueueに同意する
IQueueの仕様
「intCount {get;}」をIQueueに追加することを検討してください
TryPeekに関するフェンスで、IQueueのTryDequeue
IsEmptyは外れ値のようです。 BCLの他の多くのコレクションタイプにはありません。 それをインターフェースに追加するには、キューに追加されると想定する必要があります
TryRemoveをドロップし、Removeを「boolRemove」に変更します。 ここでは、他のコレクションクラスとの整合性を保つことが重要になります。開発者は、「コレクション内のremove()がスローされない」という多くの筋肉の記憶を持っています。 これは多くの開発者がうまくテストできない領域であり、通常の動作が変更されると多くの驚きを引き起こします。
以前の引用から@pgolebiowski
- ユーザーデータは優先度(2つの物理インスタンス)とは別のものです。
- ユーザーデータには優先度が含まれています。
- ユーザーデータが優先されます。
- まれなケース:優先順位は、他のロジック(ユーザーデータとは異なるオブジェクトに存在する)を介して取得できます。
また、5を検討することをお勧めします。ユーザーデータには、複数のフィールドの優先度が含まれています(Lucene.netで見たように)
TryPeekに関するフェンスで、キューにないことを考慮してIQueueのTryDequeue
それらはSystem / Collections / Generic / Queue.cs#L253-L295です。
一方、キューにはありませんc#
public void Remove(T element);
public bool TryRemove(T element);
IReadOnlyCollectionから継承するかどうかに関係なく、Countが必要であることを明確にするために、「int Count {get;}」をIQueueに追加することを検討してください。
わかった。 それを変更します。
IsEmptyは外れ値のようです。 BCLの他の多くのコレクションタイプにはありません。
これは@karelzによって追加された
TryRemoveをドロップし、Removeを「boolRemove」に変更します。
Remove
とTryRemove
は、そのような他の方法( Peek
とTryPeek
またはDequeue
とTryDequeue
と一致していると思います)。
- ユーザーデータには、複数のフィールドの優先度が含まれています
これは有効なポイントですが、実際にはセレクターを使用して処理することもできます(結局のところ、これは任意の関数です)。ただし、これはヒープ用です。
IsEmptyは外れ値のようです。 BCLの他の多くのコレクションタイプにはありません。
FWIW、 bool IsEmpty { get; }
はIProducerConsumerCollection<T>
に追加したいもので、それ以来何度もそこになかったことを後悔しています。 これがないと、ラッパーはCount == 0
と同等の処理を行う必要があります。これは、一部のコレクションでは、特にほとんどの並行コレクションで実装するのに大幅に効率が低下します。
@pgolebiowski現在のAPIコントラクトをホストするgithubリポジトリと、設計の根拠を含む1つまたは2つの.mdファイルを作成するというアイデアについてどう思いますか。 それが安定したら、準備ができたらCoreFxLabsまでPRを行う前に、最初の実装を構築する場所として使用できますか?
@svick
Peek
とDequeue
が優先度にout
パラメーターを使用した場合、優先度をまったく返さないオーバーロードが発生する可能性もあります。これにより、一般的な使用法が簡素化されると思います。
同意しました。 それらを追加しましょう。
優先度を無視して、優先キュー内のアイテムだけを列挙する方法があるはずですか?
良い質問。 何を提案しますか? IEnumerable<TElement> Elements { get; }
?
安定性-これは、内部的には、優先順位が
(priority, version)
ペアのようなものでなければならないことを意味すると思います
Update
を論理的なRemove
+ Enqueue
と見なすことで、これを回避できると思います。 常に同じ優先度のアイテムの最後にアイテムを追加します(基本的に、比較結果0を-1と見なします)。 動作するはずのIMO。
@benaadams
TryXメソッドは、元のBCLでは一般的なパターンではありませんでした。 並行型まで(2.0の
Dictionary.TryGetValue
が最初の例だったと思いますか?)
たとえば、TryXメソッドはキューとスタックのコアに追加されたばかりで、まだフレームワークの一部ではありません。
私はまだBCLに慣れていないことを認めます。 APIレビューミーティングから、そして最近私たちがたくさんのTry*
メソッドを追加したという事実から、私はそれがずっと長い間一般的なパターンであるという印象を受けました😉。
いずれにせよ、それは現在一般的なパターンであり、私たちはそれを使用することを恐れてはなりません。 このパターンがまだ.NETFrameworkに含まれていないという事実は、.NET Coreでの革新を妨げるものではありません。これが主な目的であり、より迅速に革新することです。
@pgolebiowski
または、この機能を優先キューから完全に削除して、代わりにヒープに追加することもできます。 これには多くの利点があります
うーん、何かがあなたがここに議題があるかもしれないと私に言います😆
今、真剣に、それは私たちがずっと目指していた実際には良い方向です- PriorityQueue
はHeap
をしない理由になることを決して意図していませんHeap
がCoreFXに組み込まれず、PowerCollectionsと並んで高度なデータ構造としてCoreFXExtensionsリポジトリに「そのまま」留まる可能性があるという事実に問題がなければ、私はそれで問題ありません。
重要な注意:このようなアプローチが必要な場合は、優先キューとヒープを一緒に設計する必要があります。 それらの目的は異なり、一方のソリューションはもう一方のソリューションでは提供されないものを提供するためです。
なぜ一緒にやる必要があるのかわかりません。 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>
2つの提案をするのは良い考えかもしれません(そして私たちは潜在的に両方を取るかもしれません):
PriorityQueue<T,U>
セレクターなし、 IQueue
(面倒です)PriorityQueue<T>
セレクターとIQueue
かなりの数の人々が上記のことをほのめかしました。
Re: @pgolebiowskiからの最新の提案: //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リポジトリのアイデアの1つは、実績のあるコミュニティメンバーに書き込み権限を付与し、主に.NETチームがアドバイス(APIレビューの専門知識を含む)を提供し、.NETチーム/ MSが必要に応じてアービターとなるコミュニティによって推進されるようにすることです。 それが私たちが着陸する場所である場合、 System.*
またはMicrosoft.*
名前空間にそれを望まない可能性があります。 (免責事項:早い段階で考えてください。まだ結論にジャンプしないでください。他にも代替のガバナンスのアイデアがあります)
TryRemove
をドロップし、Remove
をbool Remove
ます。 ここでは、他のコレクションクラスとの整合性を保つことが重要になります。開発者は、「コレクション内のremove()がスローされない」という多くの筋肉の記憶を持っています。 これは多くの開発者がうまくテストできない領域であり、通常の動作が変更されると多くの驚きを引き起こします。
👍少なくとも他のコレクションとの調整を検討する必要があります。 誰かが他のコレクションをスキャンして、 Remove
パターンを教えてもらえますか?
@ebickle現在のAPIコントラクトをホストするgithubリポジトリと、設計の根拠を含む1つまたは2つの.mdファイルを作成するというアイデアについてどう思いますか。
CoreFxLabsで直接ホストしましょう。 👍
@karelz
優先度を無視して、優先キュー内のアイテムだけを列挙する方法があるはずですか?
良い質問。 何を提案しますか?
IEnumerable<TElement> Elements { get; }
?
ええ、それか、ある種のElementsCollection
返すプロパティのどちらかがおそらく最良のオプションです。 特にDictionary<K, V>.Values
似ているので。
常に同じ優先度のアイテムの最後にアイテムを追加します(基本的に、比較結果0を-1と見なします)。 動作するはずのIMO。
それはヒープがどのように機能するかではなく、「同じ優先順位のアイテムの終わり」はありません。 同じ優先度のアイテムは、ヒープ全体に分散できます(現在の最小値に近い場合を除く)。
または、別の言い方をすれば、安定性を維持するために、挿入時だけでなく、後でアイテムがヒープ内を移動するときも、0の比較結果を-1と見なす必要があります。 そして、私はあなたが店にのようなものがあると思うversion
一緒にpriority
適切にそれを行います。
IQueue
--RemoveとTryRemoveを追加できますが、キューに入れます それらを持っていません。
それらをキューに追加するのは理にかなっていますか
? ( @ebickleは同意します)はいの場合、追加もバンドルする必要があります。
Remove
をIQueue<T>
追加するべきではないと思います; そして私はContains
が危険だとさえ提案したいと思います。 NotSupportedExceptionsもスローし始めない限り、インターフェイスの有用性と、インターフェイスを使用できるキューのタイプが制限されます。 つまり、スコープは何ですか?
それは、バニラキュー、メッセージキュー、分散キュー、ServiceBusキュー、Azure Storageキュー、ServiceFabric Reliableキューなどのためだけのものですか...(非同期メソッドを好むそれらのいくつかに光沢があります)
それはヒープがどのように機能するかではなく、「同じ優先順位のアイテムの終わり」はありません。 同じ優先度のアイテムは、ヒープ全体に分散できます(現在の最小値に近い場合を除く)。
フェアポイント。 私はそれを二分探索木として考えていました。 私が推測する私のdsの基本を磨く必要があります:)
さて、私たちはそれを異なるds(配列、バイナリ検索ツリー(または正式な名前が何であれ)で実装します- version
またはage
フィールドを維持することは、安定性を保証する価値があるとは思いません。
@ebickle現在のAPIコントラクトをホストするgithubリポジトリと、設計の根拠を含む1つまたは2つの.mdファイルを作成するというアイデアについてどう思いますか。
@karelzCoreFxLabsで直接ホストしましょう。
このドキュメントをご覧ください。
2つの提案をするのは良い考えかもしれません(そして私たちは潜在的に両方を取るかもしれません):
- PriorityQueue
セレクターなし、IQueueなし(面倒です) - PriorityQueue
セレクターとIQueue付き
私はセレクターを完全に取り除きました。 1つの統合されたPriorityQueue<T, U>
が必要な場合にのみ必要です。 2つのクラスがある場合、まあ、比較子で十分です。
追加/変更/削除する価値のあるものがあれば教えてください。
@pgolebiowski
ドキュメントはよさそうだ。 .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();
}
}
PriorityQueue
TryRemoveなしの「publicboolRemove(T element)」は長年のパターンであり、変更すると開発者のミスが発生する可能性が高いため、私はまだ投票しています。 API Reviewのクルーにチャイムを鳴らすことはできますが、それは私の頭の中で未解決の質問です。
コンストラクターで初期容量を指定したり、TrimExcess関数を使用したりすることに価値はありますか、それとも現在のマイクロ最適化です-特にIEnumerableを考慮して
私は同意するbool Remove(T element)
なしTryRemove
。 Cf. https://github.com/dotnet/corefx/blob/master/src/System.Collections/src/System/Collections/Generic/CollectionExtensions.cs#L41
@pgolebiowskiドキュメントに入れてくれてありがとう。 CoreFxLabマスターブランチに対するPRとしてドキュメントを送信していただけますか? @ianhays @safernにタグを付けると、PRをマージできるようになります。
次に、CoreFxLabの問題を使用して、特定の設計ポイントに関する詳細な議論を行うことができます。この大規模な問題よりも簡単なはずです(ここで、area-PriorityQueueを作成するために電子メールを開始しました)。
CoreFxLabプルリクエストへのリンクをここに入力してください。
APIが現在の形式(2つのクラス)で承認されている場合は、4次ヒープを使用して両方の実装を含むCoreFXLabへの別のPRを作成します(実装を参照)。 PriorityQueue<T>
は下の1つの配列を使用するだけですが、 PriorityQueue<TElement, TPriority>
は2つの配列を使用し、それらの要素を一緒に再配置します。 これにより、追加の割り当てを節約し、可能な限り効率的にすることができます。 緑色のライトが表示されたら、それを追加します。
ConcurrentQueueのようなスレッドセーフバージョンを作成する計画はありますか?
私は:heart:これの並行バージョンを見て、 IProducerConsumerCollection<T>
実装しているので、 BlockingCollection<T>
などで使用できます。
@aobatact @khellangまったく別のスレッドのように聞こえます:
私はそれが非常に価値があることに同意します:wink:!
public bool IsEmpty();
なぜこれが方法なのですか? IsEmpty
持つフレームワーク上の他のすべてのコレクションには、プロパティとしてそれがあります。
トップポストの提案をIsEmpty { get; }
更新しました。
また、corefxlabリポジトリに最新の提案ドキュメントへのリンクを追加しました。
こんにちは、みんな、
できればアップデートをサポートしたほうがいいとの声も多いと思います。 グラフ検索に便利な機能だと誰もが疑うことはなかったと思います。
しかし、あちこちでいくつかの反対意見が出されました。 だから私は私の理解を確認することができますそれは主な反対意見は次のように思われます:
-重複する要素がある場合に更新がどのように機能するかは明確ではありません。
-優先キューでサポートするために重複する要素が望ましいかどうかについては、いくつかの議論があります
-優先度を更新するためだけに、バッキングデータ構造内の要素を検索するための追加のルックアップデータ構造を用意するのは非効率的である可能性があるという懸念があります。 特に更新が実行されないシナリオの場合! および/または最悪の場合のパフォーマンス保証に影響を与えます...
私が逃したものはありますか?
OKもう1つは、意味的に「updateFirst」または「removeFirst」を意味するupdate / removeがおかしいかもしれないと主張されていると思います。 :)
PriorityQueueの使用を開始できる.NetCoreのバージョンはどれですか。
@memoryfraction .NET Core自体にはまだPriorityQueueがありません(提案はありますが、最近はそれに費やす時間がありませんでした)。 ただし、Microsoft .NETCoreディストリビューションに含める必要がある理由はありません。 コミュニティの誰もがNuGetに1つ置くことができます。 おそらく、この問題に関する誰かが提案することができます。
@memoryfraction .NET Core自体にはまだPriorityQueueがありません(提案はありますが、最近はそれに費やす時間がありませんでした)。 ただし、Microsoft .NETCoreディストリビューションに含める必要がある理由はありません。 コミュニティの誰もがNuGetに1つ置くことができます。 おそらく、この問題に関する誰かが提案することができます。
あなたの返事と提案をありがとう。
C#を使用してleetcodeを練習し、SortedSetを使用して重複する要素を持つセットを処理する必要がある場合。 この問題を解決するには、要素を一意のIDでラップする必要があります。
そのため、将来的には.NET Coreに優先度付きキューを配置する方が便利なので、優先度付きキューを使用することをお勧めします。
この問題の現状はどうなっていますか? 最近、PriorityQueueが必要になっていることに気づきました
この提案では、デフォルトでコレクションの初期化は有効になっていません。 PriorityQueue<T>
はAdd
メソッドがありません。 コレクションの初期化は、重複するメソッドの追加を保証するのに十分な大きさの言語機能だと思います。
誰かが共有したい高速のバイナリヒープを持っている場合は、それを私が書いたものと比較したいと思います。
提案どおりにAPIを完全に実装しました。 ただし、ヒープではなく、単純なソート済み配列を使用します。 最も価値の低いアイテムを最後に保持するので、通常の順序とは逆にソートされます。 アイテムの数が32未満の場合、単純な線形検索を使用して、新しい値を挿入する場所を見つけます。 その後、二分探索を使用しました。 ランダムデータを使用すると、キューを埋めてから排出するという単純なケースでは、このアプローチがバイナリヒープよりも高速であることがわかりました。
時間があれば、公開のgitリポジトリに入れて、人々がそれを批評し、このコメントを場所で更新できるようにします。
@SunnyWar現在使用しています: https :
パフォーマンスが良いです。 そこでGenericPriorityQueueに対して実装をテストすることは理にかなっていると思います
私の以前の投稿は無視してください。 テストでエラーが見つかりました。 修正されると、バイナリヒープは最高のパフォーマンスを発揮します。
@karelzこの提案は現在ICollection<T>
どこにありますか? コレクションが明らかに読み取り専用ではないのに、なぜそれがサポートされないのかわかりませんでしたか?
@karelzは、デキュー順序の未解決の質問です。最も低い優先度の値を優先して最初に削除されると主張します。 これは最短パスアルゴリズムに便利であり、タイマーキューの実装にも自然です。 そして、それらは私がそれが使用されると思う主な2つのシナリオです。 「高い」優先順位と「高い」数値に関連する言語学的なものでない限り、反対の議論が何であるかを私は本当に知りません。
@karelz列挙順序を再...どのように持っていることについてSort()
インプレースソート順に、コレクションの内部データヒープを置くタイプの方法(N Oで(Nログ))。 そして呼び出すEnumerate()
した後、 Sort()
O(n)の中でコレクションを列挙します。 そして、Sort()が呼び出されない場合、O(n)でソートされていない順序で要素を返しますか?
トリアージ:これを実装する必要があるかどうかについてのコンセンサスがないように思われるため、将来に移行します。
それを聞いて非常に残念です。 私はまだこれのためにサードパーティのソリューションを使用する必要があります。
サードパーティのソリューションを使用したくない主な理由は何ですか?
ヒープデータ構造は、leetcodeを実行するために必須です
より多くのleetcode、より多くのc#コードインタビュー、つまりより多くのc#開発者。
より多くの開発者はより良いエコシステムを意味します。
より良いエコシステムとは、明日もc#でプログラムできるかどうかを意味します。
要約すると、これは機能であるだけでなく、将来でもあります。 そのため、問題には「将来」というラベルが付けられています。
サードパーティのソリューションを使用したくない主な理由は何ですか?
標準がない場合、誰もが独自の癖を発明します。
私が取り組んでいることに関連しているので、 System.Collections.Generic.PriorityQueue<T>
を望んでいたことが何度もありました。 この提案は5年前からあります。 なぜそれはまだ起こらなかったのですか?
なぜそれはまだ起こらなかったのですか?
優先的な飢餓、しゃれは意図されていません。 コレクションをBCLに配置する場合、コレクションを交換する方法、実装するインターフェイス、セマンティクスなどを考慮する必要があるため、コレクションの設計は機能します。これはロケット科学ではありませんが、時間がかかります。 さらに、デザインを判断するための具体的なユーザーケースと顧客のセットが必要です。これは、コレクションがますます専門化されるにつれて難しくなります。 これまでのところ、それよりも重要であると考えられていた他の作業が常にありました。
最近の例を見てみましょう:不変のコレクション。 それらは、不変性に関するVSのユースケースのためにBCLチームで働いていない誰かによって設計されました。 私たちは彼と協力してAPIを「BCL化」しました。 そして、Roslynがオンラインになったとき、私たちはできるだけ多くの場所で彼らのコピーを私たちのものに置き換え、彼らのフィードバックに基づいて設計(および実装)を大幅に調整しました。 「ヒーロー」シナリオがなければ、これは難しいことです。
標準がない場合、誰もが独自の癖を発明します。
@masonwheelerはこれがPriorityQueue<T>
見たものですか? 互換性がなく、明確に受け入れられている「ほとんどの人にとって最良のライブラリ」ではないサードパーティのオプションがいくつかあるということですか? (私は調査を行っていないので、答えはわかりません)
@eiriktsarpalis実装するかどうかについて、どのようにコンセンサスがありませんか?
@terrajobstこれがよく知られたアプリケーションでよく知られているパターンである場合、本当にヒーローシナリオが必要ですか? ここではイノベーションはほとんど必要ありません。 すでにかなり完全に開発されたインターフェース仕様さえあります。 私の意見では、このユーティリティが存在しない本当の理由は、それが難しすぎるということでも、需要もユースケースもないということでもありません。 本当の理由は、実際のソフトウェアプロジェクトでは、フレームワークライブラリに配置するための政治キャンペーンを行うよりも、自分でプロジェクトを作成する方が簡単だからです。
サードパーティのソリューションを使用したくない主な理由は何ですか?
@terrajobst
私は個人的に、サードパーティのソリューションが常に期待どおりに機能するとは限らない、または現在の言語機能を使用しないという経験をしました。 標準化されたバージョンi(ユーザーとして)を使用すると、パフォーマンスが最高のものになることを確信できます。
@danmosemsft
@masonwheelerはこれが
PriorityQueue<T>
見たものですか? 互換性がなく、明確に受け入れられている「ほとんどの人にとって最良のライブラリ」ではないサードパーティのオプションがいくつかあるということですか? (私は調査を行っていないので、答えはわかりません)
はい。 「C#優先キュー」をグーグルで検索してください。 最初のページはいっぱいです:
@terrajobstこれがよく知られたアプリケーションでよく知られているパターンである場合、本当にヒーローシナリオが必要ですか? ここではイノベーションはほとんど必要ありません。
私の経験ではそうです。 悪魔は細部にあり、APIを出荷した後は、実際に重大な変更を加えることはできません。 そして、ユーザーに見える実装の選択肢はたくさんあります。 APIをOOBとしてしばらくプレビューできますが、フィードバックを確実に収集することはできますが、ヒーローシナリオがないということは、タイブレーカーがないことを意味し、ヒーローがシナリオが到着しましたが、データ構造がその要件を満たしていません。
どうやら私たちはヒーローが必要です。 😛
@masonwheelerあなたのリンクはこれにあると思いました😄今それは私の頭の中にあります。
@terrajobstが言うように、ここでの私たちの主な問題はリソース/注意です(そしてあなたが望むならそれについて私たちを責めることができます)私たちはまた、Microsoft以外のエコシステムを強化して、どんなものでも強力なライブラリを見つけることができるようにしたいと思いますシナリオ。
[わかりやすくするために編集]
@danmosemsftいや、その曲を選ぶつもりなら、シュレック2バージョンをやります。
ヒーローアプリ候補#1:TimerQueue.Portableで使用するのはどうですか?
ヒーローアプリ候補#1:TimerQueue.Portableで使用するのはどうですか?
すでに検討され、プロトタイプが作成され、破棄されています。 これにより、タイマーがすばやく作成および破棄される(タイムアウトなど)という非常に一般的なケースの効率が低下します。
@stephentoubタイマーの数が少ないシナリオでは、効率が低下すると思います。 しかし、それはどのようにスケーリングしますか?
タイマーの数が少ないシナリオでは、効率が低下することを意味していると思います。 しかし、それはどのようにスケーリングしますか?
いいえ、一般的なケースは、いつでもたくさんのタイマーがありますが、起動しているタイマーはごくわずかであるということです。 これは、タイムアウトにタイマーを使用した場合の結果です。 そして重要なことは、データ構造に追加したりデータ構造から削除したりできる速度です...それを0(1)にし、オーバーヘッドを非常に低くする必要があります。 それがO(log N)になると、問題になります。
優先キューがあると、C#は間違いなく面接に適したものになります。
優先キューがあると、C#は間違いなく面接に適したものになります。
はい、本当です。
同じ理由で探しています。
@stephentoub実際には発生しないタイムアウトについては、それは私にとって完全に理にかなっています。 しかし、突然多くのタイムアウトが発生し始めた場合、突然パケット損失や応答しないサーバーなどが発生したため、システムはどうなるのでしょうか。 また、System.Timerの定期タイマーも同じ実装を使用していますか? そこでは、タイムアウトの期限が切れるのが「ハッピーパス」になります。
しかし、突然多くのタイムアウトが発生し始めると、システムはどうなるのだろうか。
それを試してみてください。 :)
以前の実装では、実際のワークロードがかなり苦しんでいました。 私たちが調べたすべての場合において、新しいもの(優先キューを使用せず、代わりにすぐにタイマーとすぐにタイマーを分割するだけです)は問題を修正しました、そして私たちは新しいものを見ていませんそれ。
また、System.Timerの定期タイマーも同じ実装を使用していますか?
はい。 しかし、それらは一般に、実際のアプリケーションではかなり均等に分散しています。
5年後、まだPriorityQueueはありません。
5年後、まだPriorityQueueはありません。
十分に高い優先度であってはなりません...
それらはレポレイアウトを中心に変更されました。 新しい場所はhttps://github.com/dotnet/reactive/blob/master/Rx.NET/Source/src/System.Reactive/Internal/PriorityQueue.csです
@stephentoubですが、実際には@eiriktsarpalisでしょうか? 完成したAPIデザインがあれば、喜んで取り組んでいきます
これが解決されたという宣言はまだ見ていません。指定されたキラーアプリなしで最終的なAPIデザインが存在するかどうかはわかりません。 しかし...
キラーアプリのトップ候補がプログラミングコンテスト/教育/インタビューであると仮定すると、トップのエリックのデザインは十分に役立つように見えると思います...そして私はまだ反対提案を抱えています(新しく改訂されましたが、まだ撤回されていません!)
私がそれらの間で気づいている主な違いは、それが辞書のように話そうとするべきかどうか、そしてセレクターが物であるべきかどうかです。
なぜ辞書にしたかったのか、正確に忘れ始めています。 優先順位を更新するためのインデックス付きアサイナがあり、優先順位を使用してキーを列挙できることは、私には良いことであり、辞書のように思えました。 デバッガーで辞書として視覚化できるのもいいかもしれません。
セレクターは、実際には、期待されるクラスインターフェイスであるFWIWを大幅に変更すると思います。 セレクターは、構築時に組み込まれているものである必要があります。これがある場合は、他の誰かに優先度の値を渡す必要がなくなります。優先度を知りたい場合は、セレクターを呼び出すだけです。 したがって、セレクターのほとんどを除いて、メソッドシグネチャに優先度パラメーターを含めたくないでしょう。 したがって、その場合、それは一種のより専門的な「PrioritySet」クラスになります。 [不純なセレクターがバグの問題になる可能性があります!]
@TimLovellSmithこの動作の要望は理解していますが、これは5年間の質問であるため、セレクターを使用して配列ベースのバイナリヒープを実装し、 Queue.cs
と同じAPI表面積を共有するのは合理的ではないでしょうかPriorityQueue
を作成する場合は、 Queue
API設計をエミュレートする必要があると思います。
@Jlalondしたがって、ここでの提案ですね。 ヒープベースの配列の実装に異論はありません。 私がセレクターに対して持っていた最大の異議は、優先順位の更新を正しく行うのに十分簡単ではないということを今思い出します。 プレーンな古いキューには優先度更新操作はありませんが、要素の優先度の更新は、多くの優先度キューアルゴリズムにとって重要な操作です。
破損した優先度付きキュー構造をデバッグする必要のないAPIを使用する場合、人々は永遠に感謝するはずです。
@TimLovellSmith申し訳ありませんが、ティム、 IDictionary
からの継承を明確にする必要がありました。
優先順位が変わるユースケースはありますか?
私はあなたの実装が好きですが、IDictionaryBehaviorの複製を減らすことができると思います。 辞書のアクセスパターンは直感的ではないと思うので、 IDictionary<>
から継承するのではなく、ICollectionだけを継承する必要があると思います。
ただし、Tとそれに関連する優先度を返すことは理にかなっていると思いますが、データ構造内の要素をエンキューまたはデキューするときにその優先度を知る必要があるユースケースはわかりません。
@Jlalond
優先キューがある場合は、単純かつ効率的に実行できるすべての操作をサポートする必要があります。
この種のデータ構造/抽象化に精通している人々がそこにいることを「期待」しています。
更新の優先度は、これらの両方のカテゴリに分類されるため、APIに属します。 優先度の更新は、多くのアルゴリズムで十分に重要であるため、ヒープデータ構造を使用して操作を実行する複雑さは、アルゴリズム全体の複雑さに影響し、定期的に測定されます。以下を参照してください。
https://en.wikipedia.org/wiki/Priority_queue#Specialized_heaps
TBHは主に、アルゴリズムと効率に関心があり、測定されたdecrease_keyです。したがって、APIにはDecreasePriority()しかなく、実際にはUpdatePriorityがないので個人的には問題ありません。
ただし、便宜上、すべての変更が増加であるか減少であるかを心配することでAPIのユーザーを煩わせない方が
私が同意したRE辞書。 シンプルな名前で私の提案からそれを削除した方が良いです。
https://github.com/TimLovellSmith/corefxlab/blob/priorityQueueSpecSolved/docs/specs/priority-queue.md
エンキューまたはデキューするときにデータ構造内の要素の優先度を知る必要があるユースケースはわかりません。
このコメントが何なのかわかりません。 要素をデキューするために、要素の優先度を知る必要はありません。 ただし、キューに入れるときは、その優先度を知る必要があります。 要素をデキューするときに、要素の最終的な優先順位を知りたい場合があります。その値には、距離などの意味がある場合があるためです。
@TimLovellSmith
このコメントが何なのかわかりません。 要素をデキューするために、要素の優先度を知る必要はありません。 ただし、キューに入れるときは、その優先度を知る必要があります。 要素をデキューするときに、要素の最終的な優先順位を知りたい場合があります。その値には、距離などの意味がある場合があるためです。
申し訳ありませんが、キーと値のペアでTPriortyが何を意味するのか誤解しました。
さて、最後の2つの質問、なぜそれらがTPriorityでKeyValuePairであるのか、そして再優先順位付けのために私が見ることができる最良の時間はN log Nです、私はそれが価値があることに同意しますが、そのAPIはどのように見えるでしょうか?
再優先順位付けのために私が見ることができる最良の時間はNlogNです。
1つのアイテムではなく、N個のアイテムの優先順位を変更するのに最適な時期ですよね。
ドキュメントを明確にします。「UpdatePriority | O(log n)」は「UpdatePriority(single item)| O(logn)」と読み替えてください。
@TimLovellSmith理にかなっていますが、要素を削除してから再挿入する必要があるため、優先度の更新は実際にはO2 *(log n)ではありませんか? これがバイナリヒープであることに基づいて私の仮定を立てる
@TimLovellSmith理にかなっていますが、要素を削除してから再挿入する必要があるため、優先度の更新は実際にはO2 *(log n)ではありませんか? これがバイナリヒープであることに基づいて私の仮定を立てる
その2のような定数因子は、Nのサイズが大きくなるにつれてほとんど無関係になるため、複雑さの分析では一般に無視されます。
理解しましたが、ほとんどの場合、ティムに何かエキサイティングなアイデアがあるかどうかを知りたいと思っていました。
1つの操作:)
火、2020年8月18日には、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
。
@Jlalond
斬新なアイデアはありません。一定の要素を無視していましたが、減少も増加とは異なります。
優先度の更新が減少の場合は、削除して再度追加する必要はありません。バブルするだけなので、1 * O(log n)= O(log n)になります。
優先度の更新が増加である場合は、おそらくそれを削除して再度追加する必要があるため、その2 * O(log n)=まだO(log n)です。
他の誰かが、remove + readdよりも優先度を上げるためのより良いデータ構造/アルゴリズムを発明したかもしれませんが、私はそれを見つけようとしませんでした。O(log n)で十分なようです。
KeyValuePair
は、辞書である間はインターフェイスに忍び込みましたが、主に_要素のコレクションを優先度_で繰り返すことができるように、辞書が削除されても存続しました。 また、要素を優先度とともにデキューできるようにします。 ただし、簡単にするために、優先順位を指定したデキューは、「高度な」バージョンのDequeueAPIでのみ行う必要があります。 (TryDequeue:D)
私はその変更を行います。
@TimLovellSmithかっこいい、私はあなたの改訂された提案を楽しみにしています。 これに取り組み始めるために、レビューのために提出できますか? さらに、マージ時間を改善するためにペアリングキューにいくらかの推進力があったことは知っていますが、それでも配列ベースのバイナリヒープが最も全体的なパフォーマンスになると思います。 考え?
@Jlalond
Peek + Dequeueapiを簡素化するためにマイナーな変更を加えました。
いくつかのICollection
同じPRリンク。 レビューのために提出する別の方法はありますか?
配列ベースのヒープとほぼ同じかそれ以上の速度である限り、どの実装を使用してもかまいません。
@TimLovellSmith私は実際にはAPIレビューについて何も知りませんが、 @ danmosemsftが私たちを正しい方向に思います。
私の最初の質問は何でしょうか
そして、私は実際にはバイナリヒープを好みますが、私たち全員がその方向に進んで大丈夫であることを確認したかったのです
@eiriktsarpalis @layomiaはエリアの所有者であり、APIレビューを通じてこれを管理する必要があります。 私は議論をフォローしていません、私たちはコンセンサスのポイントに達しましたか?
過去に説明したように、コアライブラリは、すべてのコレクション、特に意見が分かれているコレクションやニッチなコレクションに最適な場所ではありません。 たとえば、毎年出荷しています。図書館ほど速く移動することはできません。 そのギャップを埋めるために、コレクションパックを中心にコミュニティを構築することを目標としています。 しかし、これに対する84の賛成は、コアライブラリでこれが広く必要とされていることを示唆しています。
@Jlalond申し訳ありませんが、最初の質問が何であるかが
@TimLovellSmithうん、それが一般的かどうか知りたかっただけ
@danmosemsftありがとうダン。 私の提案[1]に対して私が目にする未解決の異議/コメントの数はほぼゼロであり、 未解決のままにされた重要な質問に対処します(これはますます類似するようになっています)。
したがって、これまでのところ健全性チェックに合格していると思います。 まだいくつかのレビューが必要です。IReadOnlyCollectionを継承することが有用かどうかについて話し合うことができます(あまり有用ではないようですが、専門家に任せる必要があります)など-それがAPIレビュープロセスの目的だと思います! @eiriktsarpalis @layomiaこれを見てください。
[PS-スレッドの感情に基づいて、現在提案されているキラーアプリは、コーディングコンテスト、インタビューの質問などです。ここでは、クラスまたは構造のいずれかで機能するシンプルなAPIと、その日のお気に入りの数値タイプを適切なOで使用する必要があります。 (n)実装が遅すぎないこと-そして、いくつかの問題にそれを適用するためにギニアピッグになれることを嬉しく思います。 申し訳ありませんが、これ以上エキサイティングなことはありません。]
主にグラフ検索(問題の最適化、ルート検索など)、順序付けされた抽出(チャンク(四分木に最も近い隣人など)、スケジューリング(例:上記のタイマーの説明))。 健全性チェック/テストのために実装を直接プラグインできるプロジェクトがいくつかあります。 現在の提案は私には良さそうです(理想的な適合ではないにしても、私のすべての目的に役立つでしょう)。 私はいくつかの小さな観察を持っています:
TElement
は、 TKey
であるはずの場所に数回表示されますbool EnqueueOrUpdateIfHigherPriority
が含まれていることが多く、これが(グラフ検索などで)使用される唯一のエンキュー/更新メソッドになることがよくあります。 明らかに、これは必須ではなく、APIをさらに複雑にしますが、非常に便利です。Enqueue
コピーが2つあります( Add
意味するわけではありません):1つはbool TryEnqueue
意味しますか?EqualityComparer
の名前は少し予想外です。 KeyComparer
がPriorityComparer
と一緒になると思っていました。CopyTo
とToArray
の複雑さに関するメモがわかりません。@VisualMelon
レビューありがとうございます!
KeyComparerの名前の提案が好きです。 優先度を下げるAPIは非常に便利に聞こえます。 これらのAPIの一方または両方を追加するのはどうですか。 「最優先が最優先」モデルで使用しているので、減少のみを使用しますか? それともあなたも増やしたいですか?
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()がコレクションの先頭にある要素を返すというメンタルモデルと一致するためです。 また、バイナリヒープの場合、実装に比較は必要ないため、景品のように見えました。 しかし、多分それは私が思うよりも自由ではありません-不当な複雑さ? そのため、取り出してよかったです。
2つのエンキューは偶発的でした。 EnqueueOrUpdateがあり、優先度は常に更新されます。 TryUpdateはその論理的な逆であり、コレクションにすでに存在するアイテムの優先度は決して更新されるべきではないと思います。 それがその論理的なギャップを埋めていることはわかりますが、それが実際に人々が望むAPIであるかどうかはわかりません。
減少するバリアントを使用することしか想像できません。ノードをエンキューするか、既存のノードを優先度の低いノードに置き換えることは、ルート検索の自然な操作です。 私はすぐに反対の使用法を提案することができませんでした。 boolean
returnパラメーターは、アイテムがキューに入れられている/キューに入れられていないときに操作を実行する必要がある場合に便利です。
私はバイナリヒープを想定していませんでしたが、それが無料の場合、最初の要素が常に最小であることに問題はなく、奇妙な制約のように見えました。
@VisualMelon @TimLovellSmith 1つのAPIで問題ありませんか?
EnqueueOrUpdatePriority
? キュー内のノードを再配置する機能は、優先度が増減した場合でも、トラバーサルアルゴリズムにとって価値があると思います。
@Jlalondええ、実装によっては効率に役立つ可能性があるため、言及します。また、キューに既にあるものが改善された場合にのみ、何かをキューに入れたいというユースケースを直接エンコードします。 追加の複雑さがそのような汎用ツールに適切であるかどうかを言うとは思いませんが、それは確かに必要ではありません。
EnqueueOrIncreasePriorityを削除し、EnqueueOrUpdateとEnqueueOrDecreaseを維持するように更新しました。 HashSet.Add()のように、アイテムがコレクションに新しく追加されたときにブール値を返すようにします。
@Jlalond上記のエラーをお詫びします(この問題がいつレビューされるかを尋ねる最新のコメントの削除)。
@eiriktsarpalisが来週戻ってお伝えしたかったので、この機能の優先順位付けとAPIの確認について説明します。
これは私たちが検討していることですが、.NET6ではまだコミットされていません。
2017年にこのトピックについて数か月間(この巨大なスレッドの大部分)詳細な議論を行い、この提案をまとめて作成したにもかかわらず、このスレッドはゼロから始めてフォークすることで復活したことがわかります。上にスクロールして確認してください。 これは、 @ karelzが最初の投稿の最初の行で
corefxlabリポジトリの最新の提案を参照してください。
2017年6月16日の時点で、発生したことのないAPIレビューを待っていました。ステータスは次のとおりです。
APIが現在の形式(2つのクラス)で承認されている場合は、4次ヒープを使用して両方の実装を含むCoreFXLabへの別のPRを作成します(実装を参照)。
PriorityQueue<T>
は下の1つの配列を使用するだけですが、PriorityQueue<TElement, TPriority>
は2つの配列を使用し、それらの要素を一緒に再配置します。 これにより、追加の割り当てを節約し、可能な限り効率的にすることができます。 緑色のライトが表示されたら、それを追加します。
まだ行われていない正式なレビューから始めて、2017年に私たちが行った提案を繰り返し続けることをお勧めします。 これにより、コミュニティメンバーが何百もの投稿を交換した後にまとめられた提案の裏で、フォークや反復を回避できます。また、関係者全員の努力を尊重します。 フィードバックがあれば、喜んで話し合います。
@pgolebiowskiディスカッションに戻ってくれてありがとう。 提案をさらに効果的にフォークすることをお詫びしたいと思います。これは、コラボレーションするための最も効果的な方法ではありません。 それは私の側の衝動的な動きでした。 (提案の未解決の質問が多すぎて議論が完全に行き詰まっていて、もっと意見のある提案が必要だったという印象を受けました。)
少なくとも一時的にPRのコード内容を忘れて、これを進めて、特定された「未解決の問題」についてここで議論を再開しようとしたらどうでしょうか。 それらすべてについて意見を述べたいと思います。
クラス名PriorityQueueとヒープ<-すでに説明されていると思いますが、PriorityQueueの方が優れているというコンセンサスがあります。
IHeapとコンストラクターのオーバーロードを導入しますか? <-私は多くの価値を予見していません。 世界の95%には、複数のヒープ実装があるという説得力のある理由(パフォーマンスデルタなど)はなく、他の5%はおそらく独自に作成する必要があると思います。
IPriorityQueueを導入しますか? <-私もそれが必要だとは思いません。 他の言語やフレームワークは、標準ライブラリにこれらのインターフェイスがなくても問題なく動作すると思います。 それが正しくない場合はお知らせください。
(値内に格納されている優先度の)セレクターを使用するかどうか(5つのAPIの違い)<-優先度付きキューでセレクターをサポートすることを支持する強い議論は見られません。 IComparer<>
は、アイテムの優先順位を比較するための非常に優れた最小限の拡張メカニズムとしてすでに機能しており、セレクターは大幅な改善を提供していないと思います。 また、優先順位が変更可能なセレクターの使用方法(または使用しない方法)について混乱する可能性があります。
タプル(TElement要素、TPriority優先度)とKeyValuePairを使用するKeyValuePair
が推奨されるオプションだと思います。
PeekとDequeueは、タプルではなく引数を持たないようにする必要がありますか? <-すべての長所と短所、特にパフォーマンスについてはよくわかりません。 単純なベンチマークテストでパフォーマンスが優れているものを選択する必要がありますか?
ピークとデキューのスローはまったく役に立ちますか? <-はい...これは、キューにまだアイテムが残っていると誤って想定するアルゴリズムで発生するはずです。 アルゴリズムにそれを想定させたくない場合は、PeekとDequeueのAPIをまったく提供せず、TryPeekとTryDequeueを提供するのが最善の方法です。 [特定の場合にPeekまたはDequeueを安全に呼び出すことができるアルゴリズムがあると思います。Peek/ Dequeueを使用すると、パフォーマンスと使いやすさがわずかに向上します。]
それとは別に、検討中の提案に追加することを検討するためのいくつかの提案もあります。
PriorityQueue
PriorityQueue
PriorityQueueがあれば、怠惰なプログラマーにとっては便利でしょう。
最小の優先順位が最初に最適です。 優先度付きキューは多くの場合に使用されるため、「最小コスト」の最適化問題があります。
列挙は、順序の保証を提供するべきではありません。
未解決の問題-ハンドル:
PriorityQueue
これをより速く移動するのに役立つと考えられているので、このタイプをhttps://github.com/dotnet/runtime/discussions/42205で説明されている「コミュニティコレクション」ライブラリに移動することは理にかなってい
私は、最初に最小の優先順位が最善であることに同意します。 優先度付きキューは、ターンベースのゲームの開発にも役立ちます。優先度を、各要素が次のターンを取得するタイミングの単調に増加するカウンターにすることができると、非常に役立ちます。
これをできるだけ早くAPIレビューに持ち込むことを検討しています(ただし、.NET 6の期間内に実装を提供することはまだ約束されていません)。
ただし、これをレビューのために送信する前に、高レベルの未解決の質問のいくつかを解決する必要があります。 @TimLovellSmithの記事は、その目標を達成するための良い出発点だと思います。
それらの点に関するいくつかの意見:
質問1〜3についてのコンセンサスは長い間確立されており、解決済みとして扱うことができると思います。
_(値内に格納されている優先度の)セレクターを使用するかどうか_-コメントに同意します。 この設計のもう1つの問題は、セレクターがオプションであるということです。つまり、間違ったEnqueue
オーバーロードを呼び出さないように注意する必要があります(またはInvalidOperationException
取得するリスクがあります)。
KeyValuePair
よりもタプルを使用する方が好きです。 .Key
プロパティを使用してTPriority
値にアクセスするの.Priority
を使用できます。
_PeekとDequeueは、タプルではなく引数を持たない方がよいでしょうか?_-他の場所でも同様の方法で確立された規則に従う必要があると思います。 したがって、おそらくout
引数を使用します。
_ピークとデキューのスローはまったく役に立ちますか?_-コメントに100%同意しました。
_最初に最小の優先順位が最適です_-同意しました。
_列挙は順序保証を提供するべきではありません_-これはユーザーの期待に違反しないでしょうか? トレードオフは何ですか? 注意:APIレビューの議論のために、おそらくこのような詳細を延期することができます。
他のいくつかの未解決の質問も言い換えたいと思います。
PriorityQueue<T>
、 PriorityQueue<TElement, TPriority>
またはその両方を発送しますか? -個人的には、後者のみを実装する必要があると思います。後者は、よりクリーンでより汎用的なソリューションを提供するように思われるからです。 原則として、優先度はキューに入れられた要素に固有のプロパティであってはならないため、ユーザーにラッパータイプにカプセル化するように強制するべきではありません。
平等まで、要素の一意性が必要ですか? -間違っている場合は訂正してください。ただし、一意性は人為的な制約であり、ハンドルアプローチに頼らずに更新シナリオをサポートする必要があるために強制されていると感じています。 また、正しい等式セマンティクスを持つ要素についても心配する必要があるため、APIサーフェスが複雑になります(要素が大きなDTOである場合の適切な等式は何ですか?)。 私はここで3つの可能なパスを見ることができます:
Enqueue
メソッドバリアントを使用して取得できます。 ハンドルの割り当てが十分に大きな懸念事項である場合、それらをValueTaskで償却できるかどうか疑問に思います。キューのマージをサポートしていますか? -マージが効率的でない実装(おそらく配列ヒープを使用)を1つだけ出荷しているため、コンセンサスはないようです。
どのインターフェースを実装する必要がありますか? - IQueue<T>
推奨する提案をいくつか見ましたが、これはリークのある抽象化のように感じます。 個人的には、シンプルに保ち、 ICollection<T>
実装することを好みます。
cc @layomia @safern
@eiriktsarpalisそれどころか、更新をサポートするための制約について人為的なことは何もありません!
優先度付きキューを使用するアルゴリズムは、多くの場合、要素の優先度を更新します。 問題は、通常のオブジェクトの優先順位を更新するために「正しく機能する」オブジェクト指向APIを提供するのか、それとも人々に強制するのかということです。
a)ハンドルモデルを理解する
b)オブジェクトのハンドルを追跡するために、追加のプロパティや外部ディクショナリなどの追加データをメモリに保持して、オブジェクトが更新できるようにします(変更できないクラスのディクショナリを使用する必要がありますか?またはオブジェクトをタプルなどにアップグレードします)
c)「メモリ管理」または「ガベージコレクト」外部構造、つまり、ディクショナリアプローチを使用する場合、キューに存在しなくなったアイテムのハンドルをクリーンアップします。
d)単一の優先度キューのコンテキストでのみ意味があるため、複数のキューを持つコンテキストで不透明なハンドルを混同しないでください
さらに、_優先度によってオブジェクトを追跡する_キューがこのように動作することを誰もが望んでいる理由全体について、この哲学的な質問があります。同じオブジェクト(等しいがtrueを返す)に2つの_異なる_優先度が必要なのはなぜですか? それらが実際に異なる優先順位を持つことになっている場合、なぜそれらは異なるオブジェクトとしてモデル化されないのですか? (または、識別子を使用してタプルにアップコンバートされましたか?)
また、ハンドルの場合、ハンドルが実際に機能するように、優先度キューにハンドルの内部テーブルが必要です。 これは、キュー内のオブジェクトを検索するための辞書を保持することと同等の作業だと思います。
PS:すべての.netオブジェクトはすでに平等/一意性の概念をサポートしているので、それを「必要とする」という大きな要求ではありません。
KeyValuePair
に関しては、理想的ではないことに同意します(ただし、提案では、 Key
は要素用、 Value
は優先度用であり、さまざまなSorted
と一致しています。 BCLのstruct
を好むでしょう。
独自性に関しては、それが根本的な懸念事項であり、それまでは他に何も決まらないと思います。 目標が単一の使いやすい汎用APIである場合は、(オプションの)コンパレーターによって定義された要素の一意性(既存の提案と提案iの両方による)を優先します。 ユニークと非ユニークは大きな亀裂であり、私は両方のタイプを異なる目的で使用します。 前者は実装が「困難」であり、誤用がより困難でありながら、最も(そしてより典型的な)ユースケース(私の経験だけ)をカバーしています。 非一意性(IMO)を別のタイプ(たとえば、単純な古いバイナリヒープ)で処理する必要があるユースケース。両方を利用できるようにしていただければ幸いです。 これは基本的に、 @ pgolebiowskiによってリンクされた元の提案が(私が理解しているように)(単純な)ラッパーを法として提供するものです。 _編集:いいえ、それは同点の優先順位をサポートしません_
それどころか、更新をサポートするための制約については、人為的なものは何もありません。
申し訳ありませんが、更新のサポートが人為的なものであることを示すつもりはありませんでした。 むしろ、一意性の要件は、更新をサポートするために人為的に導入されています。
PS:すべての.netオブジェクトはすでに平等/一意性の概念をサポートしているので、それを「必要とする」という大きな要求ではありません
もちろんですが、型に付属する等式セマンティクスが望ましいものではない場合があります(たとえば、参照の等式、本格的な構造の等式など)。 平等は難しいことを指摘しているだけで、それを設計に強制すると、まったく新しいクラスの潜在的なユーザーバグが発生します。
@eiriktsarpalisそれを明確にしてくれてありがとう。 しかし、それは本当に人工的なものですか? そうではないと思います。 そのちょうど別の自然な解決策。
APIは_well-defined_である必要があります。 ユーザーが更新したいものを正確に指定する必要がない限り、更新APIを提供することはできません。 ハンドルとオブジェクトの同等性は、明確に定義されたAPIを構築するための2つの異なるアプローチにすぎません。
ハンドルアプローチ:コレクションにオブジェクトを追加するたびに、オブジェクトに「名前」、つまり「ハンドル」を付ける必要があります。これにより、会話の後半であいまいさを感じることなく、その正確なオブジェクトを参照できます。
オブジェクトの一意性アプローチ:コレクションにオブジェクトを追加するたびに、そのオブジェクトは別のオブジェクトである必要があります。または、オブジェクトが既に存在する場合の処理方法を指定する必要があります。
残念ながら、オブジェクトアプローチのみが、「EnqueueIfNotExists」や「EnqueueOrDecreasePriroity(item)」などの最も便利な高レベルAPIメソッドプロパティのいくつかを実際にサポートできます。これらは、ハンドル中心の設計で提供する意味がありません。アイテムがすでにキューに存在するかどうかを知ることはできません(ハンドルを使用してそれを追跡するのはあなたの仕事だからです)。
ハンドルアプローチに対する最も明白な批判の1つ、または一意性の制約を私に落とすことは、更新可能な優先度を持つあらゆる種類のシナリオの実装がはるかに複雑になることです。
例えば
PS
もちろんですが、型に付属する等式セマンティクスが望ましいものではない場合があります
IEqualityComparerのようなエスケープハッチ、またはよりリッチなタイプへのアップコンバートが必要です。
フィードバックをありがとう🥳すべての新しい入力を考慮して、週末に提案を更新し、次のラウンドの新しい改訂を共有します。 ETA2020-09-20。
.NET Coreコミュニティは、システムライブラリに_priority queue_機能を追加することを提案しています。これは、各要素に優先度が追加で関連付けられたデータ構造です。 具体的には、 PriorityQueue<TElement, TPriority>
をSystem.Collections.Generic
名前空間に追加することを提案します。
私たちの設計では、次の教義に導かれました(より良いものを知らない限り):
概念的には、優先度キューは要素のコレクションであり、各要素には関連付けられた優先度があります。 優先度付きキューの最も重要な機能は、コレクション内で最も優先度の高い要素への効率的なアクセスと、その要素を削除するオプションを提供することです。 予想される動作には、次のものも含まれます。1)すでにコレクションにある要素の優先度を変更する機能。 2)複数の優先キューをマージする機能。
優先度付きキューは抽象的なデータ構造です。つまり、前のセクションで説明したように、特定の動作特性を持つ概念です。 優先度付きキューの最も効率的な実装は、ヒープに基づいています。 ただし、一般的な誤解とは異なり、ヒープは抽象的なデータ構造でもあり、さまざまな方法で実現でき、それぞれに異なるメリットとデメリットがあります。
ほとんどのソフトウェアエンジニアは、配列ベースのバイナリヒープの実装にのみ精通しています。これは最も単純な実装ですが、残念ながら最も効率的な実装ではありません。 一般的なランダム入力の場合、より効率的なヒープタイプの2つの例は、 4次ヒープとペアリングヒープです。 ヒープの詳細については、ウィキペディアとこのペーパーを参照してください。
私たちの議論は、設計で最も困難な領域であると同時にAPIに最大の影響を与えるのは、更新メカニズムであることを示しています。 具体的には、お客様に提供したい製品が、コレクションにすでに存在する要素の優先度の更新をサポートする必要があるかどうか、またどのようにサポートするかを決定することが課題です。
このような機能は、ダイクストラの最短経路アルゴリズムや、優先順位の変更を処理する必要のあるジョブスケジューラなどを実装するために必要です。 更新メカニズムがJavaにありません。これは、エンジニアにとってがっかりすることが示されています。たとえば、32k回以上表示されたこれらの3つのStackOverflowの質問(例、例、例)などです。 このような限られた値のAPIの導入を回避するために、提供する優先度キュー機能の基本的な要件は、コレクションにすでに存在する要素の優先度を更新する機能をサポートすることであると考えています。
更新メカニズムを提供するには、お客様が正確に何を更新したいかについて具体的にできるようにする必要があります。 これを実現する2つの方法を特定しました。a)ハンドルを介して。 b)コレクション内の要素の一意性を強制することによって。 これらにはそれぞれ、さまざまなメリットとコストが伴います。
オプション(a):ハンドル。 このアプローチでは、要素がキューに追加されるたびに、データ構造が固有のハンドルを提供します。 顧客が更新メカニズムを使用したい場合は、後で更新する要素を明確に指定できるように、そのようなハンドルを追跡する必要があります。 このソリューションの主なコストは、顧客がこれらのポインターを管理する必要があることです。 ただし、優先キュー内のハンドルをサポートするために内部割り当てが必要であることを意味するわけではありません。非配列ベースのヒープはノードに基づいており、各ノードは自動的に独自のハンドルになります。 例として、 PairingHeap.UpdateメソッドのAPIを参照してください。
オプション(b):一意性。 このアプローチでは、顧客に2つの追加の制約が課せられます。i)優先度キュー内の要素は、特定の同等性セマンティクスに準拠する必要があります。これにより、新しいクラスの潜在的なユーザーバグが発生します。 ii)2つの等しい要素を同じキューに格納することはできません。 このコストを支払うことで、ハンドルアプローチに頼ることなく更新メカニズムをサポートできるというメリットが得られます。 ただし、一意性/同等性を利用して更新する要素を決定する実装では、追加の内部マッピングが必要になるため、O(n)ではなくO(1)で実行されます。
ハンドルを介した更新メカニズムをサポートする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();
}
}
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 }
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);
未定義の順序で、 HashSet
場合と同様に、列挙がO(n)で発生する可能性があります。 効率的な実装では、要素が順番に列挙されることを保証しながら、線形時間でヒープを列挙する機能は提供されません。これには、O(n log n)が必要になります。 コレクションの注文は.OrderBy(x => x.Priority)
で簡単に実行でき、すべての顧客がこの注文で列挙を気にするわけではないため、未定義の列挙注文を提供する方がよいと考えています。
| 言語| タイプ| ノート|
|:-:|:-:|:-:|
| Java | PriorityQueueAbstractQueue
を拡張し、インターフェースQueue
を実装します。 |
| さび| BinaryHeap | |
| スイフト| CFBinaryHeap | |
| C ++ | priority_queue | |
| Python | heapq | |
| 行く| ヒープ| ヒープインターフェイスがあります。 |
データ構造について説明する場合、「ヒープ」という用語は「優先度付きキュー」の4倍の頻度で使用されることに注意してください。
"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件の結果確認してください。フィードバックを受け取って、繰り返し続けてください:)私たちは収束していると感じています! 😄また、質問を喜んで受け取ります—それらをFAQに追加します!
@pgolebiowski私はハンドルベースのAPIを使用する傾向はありませんが(既存のコードを再調整する場合は、例2のようにこれをラップすることを期待します)、ほとんどの場合、見栄えがします。 私はあなたが言及した実装を試してみて、それがどのように感じられるかを確認するかもしれませんが、ここにいくつかのコメントがあります:
TryDequeue
がノードを返すのは奇妙に思えます。なぜなら、それはその時点では実際にはハンドルではないからです(2つの別々の出力パラメーターが望ましいです)Merge
なければなりませんPriorityQueue'2
ませんPriorityQueueNode'2
、そしてあなたが行動を明確にすることができますか? 私はペアリングヒープに精通していませんが、おそらく2つのヒープはその後オーバーラップしますContains
という名前のファンではありません。これはTryGet
スタイルのメソッドで推測される名前ではありません。Contains
目的でカスタムIEqualityComparer<TElement>
をサポートする必要がありますか?Remove
返品がbool
であるのは奇妙です。 TryRemove
と呼ばれるか、スローされると思います(ノードがヒープ内にない場合は、 Update
がスローすると想定しています)。@VisualMelonフィードバックをありがとう
TryDequeue
がノードを返すのは奇妙に思えます。なぜなら、それはその時点では実際にはハンドルではないからです(2つの別々の出力パラメーターが望ましいです)- 以下のためのパラメータ
Merge
なければなりませんPriorityQueue'2
ませんPriorityQueueNode'2
、そしてあなたが行動を明確にすることができますか? 私はペアリングヒープに精通していませんが、おそらく2つのヒープはその後オーバーラップしますRemove
返品がbool
であるのは奇妙です。TryRemove
と呼ばれるか、スローされると思います(ノードがヒープ内にない場合は、Update
がスローすると想定しています)。- 私は2パラメーターメソッドの
Contains
という名前のファンではありません。これはTryGet
スタイルのメソッドで推測される名前ではありません。
これら2つの説明:
- 2つの要素が同じ優先度でキューに入れられた場合、それらは予測可能な順序でデキューされるという点で安定していますか? (再現性のために持っているのはいいです;そうでなければ消費者によって十分に簡単に実装することができます)
- ノードがまだヒープ内にあるかどうかを効率的に判断する方法はないようです(これをいつ使用するかはわかりません)。
最初のポイントとして、目標が再現性である場合、実装は決定論的です。 目標が_同じ優先度でキューに配置された2つの要素の場合、それらはキューに配置されたのと同じ順序で出力されます_ —実装を次のように調整できるかどうかはわかりませんこの特性を達成すると、簡単な答えは「おそらくいいえ」になります。
2番目のポイントについては、はい、ヒープは要素がコレクションに存在するかどうかを確認するのに適していません。顧客は、O(1)でこれを達成するためにこれを個別に追跡するか、ハンドルに使用するマッピングを再利用する必要があります。それらは更新メカニズムを使用します。 それ以外の場合、O(n)。
- クラスは
Contains
目的でカスタムIEqualityComparer<TElement>
をサポートする必要がありますか?
うーん...おそらくContains
をこの優先キューの責任に置くのLinq
のメソッドを使用するだけで十分かもしれないと思い始めています( Contains
とにかく列挙に適用する必要があります)。
@pgolebiowski説明をありがとう
最初のポイントとして、目標が再現性である場合、実装は決定論的です、はい
永久に保証されているように(たとえば、実装が変更されても、動作は変更されない)、それほど決定論的ではないので、私が求めていた答えは「いいえ」だと思います。 それは問題ありません。コンシューマーは、必要に応じてシーケンスIDを優先度に追加できますが、その時点でSortedSet
がその役割を果たします。
2番目のポイントについては、はい、ヒープは要素がコレクションに存在するかどうかを確認するのに適していません。顧客は、O(1)でこれを達成するためにこれを個別に追跡するか、ハンドルに使用するマッピングを再利用する必要があります。それらは更新メカニズムを使用します。 それ以外の場合、O(n)。
Remove
必要な作業のサブセットは必要ありませんか? はっきりしていなかったかもしれません。 PriorityQueueNode
して、ヒープ内にあるかどうかを確認することを意味しました( TElement
はありません)。
うーん...おそらくこの優先キューの責任にContainsを入れるのは多すぎるのではないかと思い始めています
Contains
がなかったとしても文句は言いません。これは、ハンドルを使用する必要があることに気付いていない人にとっても罠です。
@pgolebiowskiあなたはハンドルをかなり強く支持しているようですが、効率上の理由からそれを考えるのは正しいですか?
効率の観点から、独自性のあるシナリオとないシナリオの両方で、ハンドルが本当に最適であると思うので、フレームワークによって提供される主要なソリューションとしてそれで問題ありません。
まったく:
使いやすさの観点から、重複する要素が私が望むものになることはめったにないと思います。それでも、フレームワークが両方のモデルをサポートすることが価値があるかどうか疑問に思います。 しかし...ユニークな要素に適した「PrioritySet」クラスは、提案されたPriorityQueue
ラッパーとして、たとえば、よりフレンドリーなAPIに対する継続的な需要に応えて、少なくとも後で簡単に追加できます。 (需要が存在する場合。私が思うにそうかもしれません!)
現在提案されているAPIについて、いくつかの考え/質問があります。
TryPeek(out TElement element, out TPriority priority)
オーバーロードも提供するのはどうですか?Remove(PriorityQueueNode)
スローされますか? またはfalseを返しますか?TryRemove()
バージョンがあるべきですか?Contains()
apiがほとんどの場合役立つかどうかわかりませんか? 「含む」は、特に、異なる優先順位を持つ「重複する」要素、または他の異なる機能を持つシナリオの場合、見る人の目に見えるようです! その場合、エンドユーザーはおそらくとにかく独自の検索を行う必要があります。 ただし、少なくとも、重複のないシナリオには役立ちます。@pgolebiowski新しい提案の草案作成に時間を
Contains()
またはTryGetNode()
かが存在するかどうかはわかりません。 これらは、 TElement
同等性が重要であることを意味します。これは、おそらく、ハンドルベースのアプローチが回避しようとしていたことの1つです。public void Dequeue(out TElement element);
をpublic TElement Dequeue();
と言い換えるでしょうTryDequeue()
メソッドが優先度を返す必要があるのはなぜですか?ICollection<T>
またはIReadOnlyCollection<T>
も実装するべきではありませんか?ほとんどのソフトウェアエンジニアは、配列ベースのバイナリヒープの実装にのみ精通しています。これは最も単純な実装ですが、残念ながら最も効率的な実装ではありません。 一般的なランダム入力の場合、より効率的なヒープタイプの2つの例は、4次ヒープとペアリングヒープです。
後者のアプローチを選択するためのトレードオフは何ですか? それらに配列ベースの実装を使用することは可能ですか?
「追加」や「削除」などの適切な署名がない場合、 ICollection<T>
または実装できません。IReadOnlyCollection<T>
精神/形がICollection<KeyValuePair<T,Priority>>
はるかに近い
精神/形が
ICollection<KeyValuePair<T,Priority>>
はるかに近い
順序付けは実質的にキーイングメカニズムであるTPriorityによって行われるため、 KeyValuePair<TPriority, TElement>
ではないでしょうか。
OK、全体的にメソッドContains
とTryGet
を削除することに賛成しているようです。 次のリビジョンでそれらを削除し、FAQで削除の理由を説明します。
実装されたインターフェースに関しては— IEnumerable<PriorityQueueNode<TElement, TPriority>>
十分ではありませんか? どのような機能が欠けていますか?
KeyValuePair
、 .Element
と.Priority
タプルまたは構造体の方が望ましいという声がいくつかありました。 私はこれらに賛成だと思います。
順序付けは実質的にキーイングメカニズムであるTPriorityによって行われるため、
KeyValuePair<TPriority, TElement>
ではないでしょうか。
双方にとって良い議論があります。 一方で、はい、まさにあなたが今言ったことです。 一方、KVPコレクション内のキーは通常、一意であることが期待されており、同じ優先度を持つ複数の要素を持つことは完全に有効です。
一方、KVPコレクションのキーは、通常、一意であると予想されます
私はこの声明に同意しません-キーと値のペアのコレクションはまさにそれです。 一意性の要件は、その上に階層化されます。
IEnumerable<PriorityQueueNode<TElement, TPriority>>
不十分ですか? どのような機能が欠けていますか?
提供されたAPIはすでにそのインターフェースをほぼ満たしているので、個人的にはIReadOnlyCollection<PQN<TElement, TPriority>>
が実装されることを期待しています。 さらに、それは他のコレクションタイプと一致します。
インターフェースについて:
`` `
public bool TryGetNode(TElement element、out 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サーフェス以外のものにジャンプします。
このような機能は、ダイクストラの最短経路アルゴリズムや、優先順位の変更を処理する必要のあるジョブスケジューラなどを実装するために必要です。 更新メカニズムはJavaにありません。これは、エンジニアにとってがっかりすることが示されています。たとえば、32k回以上表示されたこれらの3つのStackOverflowの質問(例、例、例)などです。 このような限られた値のAPIの導入を回避するために、提供する優先度キュー機能の基本的な要件は、コレクションにすでに存在する要素の優先度を更新する機能をサポートすることであると考えています。
反対したいのですが。 前回、C ++でDijkstraを作成しました。std:: priority_queueで十分であり、優先度の更新を処理しません。 その場合のAFAIKの一般的なコンセンサスは、優先度と値が変更された偽の要素をキューに追加し、値を処理するかどうかを監視することです。 ジョブスケジューラでも同じことができます。
正直なところ、現在のキューの提案でダイクストラがどのように見えるかはわかりません。 更新の優先順位が必要なノードを追跡するにはどうすればよいですか? TryGetNode()を使用しますか? または、ノードの別のコレクションがありますか? 現在の提案のコードを見たいです。
ウィキペディアを調べると、優先度付きキューの優先度を更新するという仮定はありません。 その機能を持たず、それを回避した他のすべての言語についても同じです。 「もっと良くなるように努力する」ことは知っていますが、実際にそれに対する需要はありますか?
一般的なランダム入力の場合、より効率的なヒープタイプの2つの例は、4次ヒープとペアリングヒープです。 ヒープの詳細については、ウィキペディアとこのペーパーを参照してください。
私は紙を調べました、そしてこれはそれからの引用です:
結果は、実装の最適な選択が入力に強く依存していることを示しています。 さらに、主にL1-L2バリアで、キャッシュパフォーマンスを最適化するように注意する必要があることを示しています。 これは、複雑でキャッシュを意識しない構造は、単純なキャッシュを意識する構造と比較して、うまく機能する可能性が低いことを示唆しています。
見た目からすると、現在のキューの提案は配列ではなくツリーの実装の背後にロックされ、メモリの周りに散在するツリーノードが要素の配列ほどパフォーマンスが高くない可能性があることがわかります。
配列とペアリングヒープに基づいて単純なバイナリヒープを比較するベンチマークがあると、適切な決定を下すのに理想的だと思います。その前に、特定の実装の背後で設計をロックするのは賢明ではないと思います( Merge
探しています)
別のトピックにジャンプして、私は個人的にKeyValuePairを持っていることを好みます
欠点はTPriority Key
見ると複雑な気持ちですが、良いドキュメントでそれを解決できると思います。
`
ダイクストラの回避策として、キューに偽の要素を追加すると機能し、処理するグラフエッジの総数は変わりません。 ただし、エッジの処理によって生成されたヒープに常駐している一時ノードの数は変化するため、メモリ使用量とエンキューおよびデキューの効率に影響を与える可能性があります。
IReadOnlyCollectionを実行できないことについては間違っていましたが、それで問題ありません。 そのインターフェイスにはAdd()とRemove()はありません。 (私が考えていたことは何でしょう...)
@ Ivanidzo4kaあなたのコメントは、2つの別々のタイプを持つことが理にかなっていることをさらに確信させます。1つは単純なバイナリヒープ(つまり、更新なし)で、もう1つは提案の1つで説明されています。
過去には、10年前に書いたバイナリヒープと、 SortedSet
/ Dictionary
組み合わせをほとんど使用していましたが、使用したシナリオがいくつかあります。両方のタイプが別々の役割を果たします(KSSPが思い浮かびます)。
これはクラスではなく構造体であるため、NullReference例外はありません
私はそれが逆だと主張したいと思います。誰かがデフォルト値を渡している場合、私は彼らにNREを見てもらいたいです。 ただし、これらが使用される場所を明確にする必要があると思います。ノード/ハンドルはおそらくクラスである必要がありますが、ペアを読み取り/返すだけの場合は、構造体である必要があることに同意します。
ハンドルベースの提案では、要素と優先度を更新できるようにする必要があることを提案したいと思います。 ハンドルを削除して新しいハンドルを追加するだけでも同じ効果が得られますが、これは便利な操作であり、実装によってはパフォーマンスが向上する場合があります(たとえば、ヒープによっては、比較的安価に優先度を下げることができます)。 このような変更により、多くのこと(たとえば、既存のAlgoKit ParingHeapに基づくこのやや悪夢を誘発する例)、特に状態空間の未知の領域で動作するものを実装するのがより整然となります。
.NET Coreコミュニティは、システムライブラリに_priority queue_機能を追加することを提案しています。これは、各要素に優先度が追加で関連付けられたデータ構造です。 具体的には、 PriorityQueue<TElement, TPriority>
をSystem.Collections.Generic
名前空間に追加することを提案します。
私たちの設計では、次の教義に導かれました(より良いものを知らない限り):
概念的には、優先度キューは要素のコレクションであり、各要素には関連付けられた優先度があります。 優先度付きキューの最も重要な機能は、コレクション内で最も優先度の高い要素への効率的なアクセスと、その要素を削除するオプションを提供することです。 予想される動作には、次のものも含まれます。1)すでにコレクションにある要素の優先度を変更する機能。 2)複数の優先キューをマージする機能。
優先度付きキューは抽象的なデータ構造です。つまり、前のセクションで説明したように、特定の動作特性を持つ概念です。 優先度付きキューの最も効率的な実装は、ヒープに基づいています。 ただし、一般的な誤解とは異なり、ヒープは抽象的なデータ構造でもあり、さまざまな方法で実現でき、それぞれに異なるメリットとデメリットがあります。
ほとんどのソフトウェアエンジニアは、配列ベースのバイナリヒープの実装にのみ精通しています。これは最も単純な実装ですが、残念ながら最も効率的な実装ではありません。 一般的なランダム入力の場合、より効率的なヒープタイプの2つの例は、 4次ヒープとペアリングヒープです。 ヒープの詳細については、ウィキペディアとこのペーパーを参照してください。
私たちの議論は、設計で最も困難な領域であると同時にAPIに最大の影響を与えるのは、更新メカニズムであることを示しています。 具体的には、お客様に提供したい製品が、コレクションにすでに存在する要素の優先度の更新をサポートする必要があるかどうか、またどのようにサポートするかを決定することが課題です。
このような機能は、ダイクストラの最短経路アルゴリズムや、優先順位の変更を処理する必要のあるジョブスケジューラなどを実装するために必要です。 更新メカニズムがJavaにありません。これは、エンジニアにとってがっかりすることが示されています。たとえば、32k回以上表示されたこれらの3つのStackOverflowの質問(例、例、例)などです。 このような限られた値のAPIの導入を回避するために、提供する優先度キュー機能の基本的な要件は、コレクションにすでに存在する要素の優先度を更新する機能をサポートすることであると考えています。
更新メカニズムを提供するには、お客様が正確に何を更新したいかについて具体的にできるようにする必要があります。 これを実現する2つの方法を特定しました。a)ハンドルを介して。 b)コレクション内の要素の一意性を強制することによって。 これらにはそれぞれ、さまざまなメリットとコストが伴います。
オプション(a):ハンドル。 このアプローチでは、要素がキューに追加されるたびに、データ構造が固有のハンドルを提供します。 顧客が更新メカニズムを使用したい場合は、後で更新する要素を明確に指定できるように、そのようなハンドルを追跡する必要があります。 このソリューションの主なコストは、顧客がこれらのポインターを管理する必要があることです。 ただし、優先キュー内のハンドルをサポートするために内部割り当てが必要であることを意味するわけではありません。非配列ベースのヒープはノードに基づいており、各ノードは自動的に独自のハンドルになります。 例として、 PairingHeap.UpdateメソッドのAPIを参照してください。
オプション(b):一意性。 このアプローチでは、顧客に2つの追加の制約が課せられます。i)優先度キュー内の要素は、特定の同等性セマンティクスに準拠する必要があります。これにより、新しいクラスの潜在的なユーザーバグが発生します。 ii)2つの等しい要素を同じキューに格納することはできません。 このコストを支払うことで、ハンドルアプローチに頼ることなく更新メカニズムをサポートできるというメリットが得られます。 ただし、一意性/同等性を利用して更新する要素を決定する実装では、追加の内部マッピングが必要になるため、O(n)ではなくO(1)で実行されます。
ハンドルを介した更新メカニズムをサポートする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();
}
}
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 }
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);
未定義の順序で、 HashSet
場合と同様に、列挙がO(n)で発生する可能性があります。 効率的な実装では、要素が順番に列挙されることを保証しながら、線形時間でヒープを列挙する機能は提供されません。これには、O(n log n)が必要になります。 コレクションの注文は.OrderBy(x => x.Priority)
で簡単に実行でき、すべての顧客がこの注文で列挙を気にするわけではないため、未定義の列挙注文を提供する方がよいと考えています。
Contains
またはTryGet
メソッドがないのはなぜですか?ヒープ内の要素を見つけるにはコレクション全体の列挙が必要であるため、このようなメソッドを提供してもほとんど価値がありません。つまり、 Contains
またはTryGet
メソッドは列挙のラッパーになります。 さらに、要素がコレクションに存在するかどうかを確認するには、優先度付きキューがTElement
オブジェクトの同等性チェックを実行する方法を認識している必要があります。これは、優先度付きキューの責任に該当すると思われるものではありません。
PriorityQueueNode
を返すDequeue
およびTryDequeue
オーバーロードがあるのPriorityQueueNode
なぜですか?これは、 Update
またはRemove
方法を使用して、ハンドルを追跡したいお客様向けです。 優先キューから要素をデキューしている間、ハンドル追跡システムの状態を調整するために使用できるハンドルを受け取ります。
Update
またはRemove
メソッドが別のキューからノードを受信するとどうなりますか?優先キューは例外をスローします。 LinkedListNode<T>
が属するLinkedList<T>
認識するのと同様に、各ノードはそれが属する優先キューを認識します。
| 言語| タイプ| ノート|
|:-:|:-:|:-:|
| Java | PriorityQueueAbstractQueue
を拡張し、インターフェースQueue
を実装します。 |
| さび| BinaryHeap | |
| スイフト| CFBinaryHeap | |
| C ++ | priority_queue | |
| Python | heapq | |
| 行く| ヒープ| ヒープインターフェイスがあります。 |
データ構造について説明する場合、「ヒープ」という用語は「優先度付きキュー」の4倍の頻度で使用されることに注意してください。
"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
削除しました。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)
オーバーロードを追加しました。PriorityQueueNode
を返すDequeue
とTryDequeue
オーバーロードがあるのですか?_Update
またはRemove
メソッドが別のキューからノードを受信するとどうなりますか?_変更メモをありがとう;)
明確化のための小さな要求:
@pgolebiowski提案されたMerge
メソッドについて:
public void Merge(PriorityQueue<TElement, TPriority> other); // O(1)
明らかに、そのような操作にはコピーセマンティクスがないので、マージが実行された後(たとえば、いずれかのインスタンスが失敗した後)にthis
とother
変更を加えることに関して何か問題があるのではないかと思います。ヒーププロパティを満たすため)。
@eiriktsarpalis @VisualMelon —ありがとう! 提起されたポイント、ETA2020-10-04に対処します。
他の人がこれ以上のフィードバック/質問/懸念/考えを持っている場合—共有してください😊
.NET Coreコミュニティは、システムライブラリに_priority queue_機能を追加することを提案しています。これは、各要素に優先度が追加で関連付けられたデータ構造です。 具体的には、 PriorityQueue<TElement, TPriority>
をSystem.Collections.Generic
名前空間に追加することを提案します。
私たちの設計では、次の教義に導かれました(より良いものを知らない限り):
概念的には、優先度キューは要素のコレクションであり、各要素には関連付けられた優先度があります。 優先度付きキューの最も重要な機能は、コレクション内で最も優先度の高い要素への効率的なアクセスと、その要素を削除するオプションを提供することです。 予想される動作には、次のものも含まれます。1)すでにコレクションにある要素の優先度を変更する機能。 2)複数の優先キューをマージする機能。
優先度付きキューは抽象的なデータ構造です。つまり、前のセクションで説明したように、特定の動作特性を持つ概念です。 優先度付きキューの最も効率的な実装は、ヒープに基づいています。 ただし、一般的な誤解とは異なり、ヒープは抽象的なデータ構造でもあり、さまざまな方法で実現でき、それぞれに異なるメリットとデメリットがあります。
ほとんどのソフトウェアエンジニアは、配列ベースのバイナリヒープの実装にのみ精通しています。これは最も単純な実装ですが、残念ながら最も効率的な実装ではありません。 一般的なランダム入力の場合、より効率的なヒープタイプの2つの例は、 4次ヒープとペアリングヒープです。 ヒープの詳細については、ウィキペディアとこのペーパーを参照してください。
私たちの議論は、設計で最も困難な領域であると同時にAPIに最大の影響を与えるのは、更新メカニズムであることを示しています。 具体的には、お客様に提供したい製品が、コレクションにすでに存在する要素の優先度の更新をサポートする必要があるかどうか、またどのようにサポートするかを決定することが課題です。
このような機能は、ダイクストラの最短経路アルゴリズムや、優先順位の変更を処理する必要のあるジョブスケジューラなどを実装するために必要です。 更新メカニズムがJavaにありません。これは、エンジニアにとってがっかりすることが示されています。たとえば、32k回以上表示されたこれらの3つのStackOverflowの質問(例、例、例)などです。 このような限られた値のAPIの導入を回避するために、提供する優先度キュー機能の基本的な要件は、コレクションにすでに存在する要素の優先度を更新する機能をサポートすることであると考えています。
更新メカニズムを提供するには、お客様が正確に何を更新したいかについて具体的にできるようにする必要があります。 これを実現する2つの方法を特定しました。a)ハンドルを介して。 b)コレクション内の要素の一意性を強制することによって。 これらにはそれぞれ、さまざまなメリットとコストが伴います。
オプション(a):ハンドル。 このアプローチでは、要素がキューに追加されるたびに、データ構造が固有のハンドルを提供します。 顧客が更新メカニズムを使用したい場合は、後で更新する要素を明確に指定できるように、そのようなハンドルを追跡する必要があります。 このソリューションの主なコストは、顧客がこれらのポインターを管理する必要があることです。 ただし、優先キュー内のハンドルをサポートするために内部割り当てが必要であることを意味するわけではありません。非配列ベースのヒープはノードに基づいており、各ノードは自動的に独自のハンドルになります。 例として、 PairingHeap.UpdateメソッドのAPIを参照してください。
オプション(b):一意性。 このアプローチでは、顧客に2つの追加の制約が課せられます。i)優先度キュー内の要素は、特定の同等性セマンティクスに準拠する必要があります。これにより、新しいクラスの潜在的なユーザーバグが発生します。 ii)2つの等しい要素を同じキューに格納することはできません。 このコストを支払うことで、ハンドルアプローチに頼ることなく更新メカニズムをサポートできるというメリットが得られます。 ただし、一意性/同等性を利用して更新する要素を決定する実装では、追加の内部マッピングが必要になるため、O(n)ではなくO(1)で実行されます。
ハンドルを介した更新メカニズムをサポートする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();
}
}
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 }
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);
未定義の順序で、 HashSet
場合と同様に、列挙がO(n)で発生する可能性があります。 効率的な実装では、要素が順番に列挙されることを保証しながら、線形時間でヒープを列挙する機能は提供されません。これには、O(n log n)が必要になります。 コレクションの注文は.OrderBy(x => x.Priority)
で簡単に実行でき、すべての顧客がこの注文で列挙を気にするわけではないため、未定義の列挙注文を提供する方がよいと考えています。
Contains
またはTryGet
メソッドがないのはなぜですか?ヒープ内の要素を見つけるにはコレクション全体の列挙が必要であるため、このようなメソッドを提供してもほとんど価値がありません。つまり、 Contains
またはTryGet
メソッドは列挙のラッパーになります。 さらに、要素がコレクションに存在するかどうかを確認するには、優先度付きキューがTElement
オブジェクトの同等性チェックを実行する方法を認識している必要があります。これは、優先度付きキューの責任に該当すると思われるものではありません。
PriorityQueueNode
を返すDequeue
およびTryDequeue
オーバーロードがあるのPriorityQueueNode
なぜですか?これは、 Update
またはRemove
方法を使用して、ハンドルを追跡したいお客様向けです。 優先キューから要素をデキューしている間、ハンドル追跡システムの状態を調整するために使用できるハンドルを受け取ります。
Update
またはRemove
メソッドが別のキューからノードを受信するとどうなりますか?優先キューは例外をスローします。 LinkedListNode<T>
が属するLinkedList<T>
認識するのと同様に、各ノードはそれが属する優先キューを認識します。 さらに、ノードがキューから削除されている場合、そのノードでUpdate
またはRemove
を呼び出そうとすると、例外が発生します。
Merge
メソッドがないのはなぜですか?2つの優先キューのマージは一定時間で実現できるため、顧客に提供するのは魅力的な機能です。 ただし、そのような機能に対する需要があることを示すデータはなく、パブリックAPIに含めることを正当化することはできません。 さらに、そのような機能の設計は重要であり、この機能が必要ない場合があることを考えると、APIの表面と実装を不必要に複雑にする可能性があります。
それでも、 Merge
メソッドを含めないことは双方向の扉です。将来、顧客がマージ機能のサポートに関心を示した場合、 PriorityQueue
タイプを拡張することが可能になります。 そのため、 Merge
メソッドをまだ含めず、リリースを進めることをお勧めします。
コレクションは、すぐに使用できる安定性の保証を提供しません。つまり、2つの要素が同じ優先度でキューに入れられている場合、顧客はそれらが特定の順序でキューから取り出されると想定できません。 ただし、お客様がPriorityQueue
を使用して安定性を実現したい場合は、それを保証するTPriority
と対応するIComparer<TPriority>
を定義できます。 さらに、データ収集は決定論的です。つまり、特定の一連の操作に対して、データ収集は常に同じように動作し、再現性があります。
| 言語| タイプ| ノート|
|:-:|:-:|:-:|
| Java | PriorityQueueAbstractQueue
を拡張し、インターフェースQueue
を実装します。 |
| さび| BinaryHeap | |
| スイフト| CFBinaryHeap | |
| C ++ | priority_queue | |
| Python | heapq | |
| 行く| ヒープ| ヒープインターフェイスがあります。 |
データ構造について説明する場合、「ヒープ」という用語は「優先度付きキュー」の4倍の頻度で使用されることに注意してください。
"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)
削除しました。Merge
メソッドがないのですか?新しいFAQは素晴らしく見えます。 提案されたAPIに対して、ハンドル辞書を使用してダイクストラをコーディングしてみましたが、基本的には問題ないようでした。
これを行うことで私が学んだ小さなことの1つは、メソッド名/オーバーロードの現在のセットは、 out
変数の暗黙的な型指定ではうまく機能しないということ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);
}
}
}
私の見解では、未解決の主な設計上の質問は、実装が優先度の更新をサポートする必要があるかどうかです。 私はここで3つの可能なパスを見ることができます:
これらのアプローチは相互に排他的であり、それぞれ独自のトレードオフがあります。
上記のアプローチのどれがほとんどのユーザーに最高の価値を提供できるかを特定することが重要です。 そのため、最も一般的な使用パターンをよりよく理解するために、ヒープ/ PriorityQueue実装のインスタンスについて、Microsoftの内部およびパブリックの両方の.NETコードベースを調べてきました。
ほとんどのアプリケーションは、ヒープソートまたはジョブスケジューリングのバリエーションを実装しています。 トポロジカルソートやハフマンコーディングなどのアルゴリズムには、いくつかのインスタンスが使用されました。 グラフの距離の計算には、より小さな数値が使用されました。 調査した80のPriorityQueue実装のうち、何らかの形式の優先度更新を実装したのは9つだけでした。
同時に、Python、Java、C ++、Go、Swift、またはRustコアライブラリのいずれのヒープ/ PriorityQueue実装も、APIの優先度更新をサポートしていません。
このデータに照らして、.ΝΕΤには、重要なAPI(heapify / push / peek / pop)を公開し、特定のパフォーマンス保証を提供し(たとえば、エンキューごとに追加の割り当てがない)、強制しないベースラインPriorityQueue実装が必要であることは明らかです。一意性の制約。 これは、実装が_O(log n)優先度の更新をサポートしない_ことを意味します。
また、_O(log n)_の更新/削除をサポートし、等式ベースのアプローチを使用する別のヒープ実装のフォローアップを検討する必要があります。 これは特殊なタイプであるため、一意性の要件はそれほど問題にはなりません。
私は両方の実装のプロトタイピングに取り組んでおり、まもなくAPI提案をフォローアップします。
分析ありがとうございます、@ eiriktsarpalis! 特に、Microsoftの内部コードベースを分析して、関連するユースケースを見つけ、データとの議論をサポートする時間を割いていただき、ありがとうございます。
ハンドルベースのアプローチは、キューに入れられた要素ごとに少なくとも1つの追加の割り当てを意味します。
この仮定は誤りです。ノードベースのヒープに追加の割り当てを行う必要はありません。 ペアリングヒープは、配列ベースのバイナリヒープよりも一般的なランダム入力に対してパフォーマンスが高くなります。つまり、更新をサポートしない優先度キューに対しても、このノードベースのヒープを使用するインセンティブがあります。 先ほど参照し
調査した80のPriorityQueue実装のうち、何らかの形式の優先度更新を実装したのは9つだけでした。
この小さなサンプルを与えられたとしても、それはすべての使用量の11〜12%です。 また、これは、ビデオゲームなどの特定のドメインでは過小評価されている可能性があり、この割合はもっと高くなると予想されます。
このデータに照らして、私には明らかです[...]
重要な仮定の1つが誤りであり、顧客の11〜12%が十分に重要なユースケースであるかどうかは議論の余地があることを考えると、そのような結論は明確ではないと思います。 私があなたの評価に欠けているのは、このメカニズムを気にしない顧客のための「更新をサポートするデータ構造」のコストへの影響の評価です。データ構造を使用できるため、このコストはごくわずかです。ハンドル機構の影響を受けません。
基本的:
| | 11〜12%のユースケース| 88〜89%のユースケース|
|:-:|:-:|:-:|
| 更新を気にする| はい| いいえ|
| ハンドルによって悪影響を受ける| 該当なし(必要です)| いいえ|
| ハンドルの影響を受けます| はい| いいえ|
私にとって、これは88〜89%だけでなく、100%のユースケースをサポートすることを支持するのは簡単です。
この仮定は誤りです。ノードベースのヒープに追加の割り当てを行う必要はありません。
優先度と項目の両方が値型である場合(または両方が所有していない参照型であるか、基本型を変更できない場合)、追加の割り当てが不要であることを示す実装にリンクできますか(またはそれがどのように達成されたかを説明してください)? それを見ると役に立ちます。 ありがとう。
もっと詳しく説明したり、言いたいことを言ったりできると助かります。 曖昧さを解消する必要があります。ピンポンがあり、それは長い議論になります。 または、電話を手配することもできます。
呼び出し元側であろうと実装側であろうと、割り当てを必要とするEnqueue操作を回避したいと言っています(実装で使用される配列を拡張するなど、償却された内部割り当ては問題ありません)。 ノードベースのヒープでそれがどのように可能であるかを理解しようとしています(たとえば、これらのノードオブジェクトが呼び出し元に公開されている場合、不適切な再利用/エイリアシングに関する懸念のために実装によるプーリングが禁止されます)。 私は書くことができるようになりたい:
C#
pq.Enqueue(42, 84);
割り当てないでください。 あなたが参照する実装はそれをどのように達成しますか?
またはあなたが言おうとしていることを言うだけです
私はそうだと思った。
割り当てを必要とするエンキュー操作を回避したい[...]次のように記述できるようにしたい:
pq.Enqueue(42, 84);
そしてそれを割り当てないようにします。
この欲求はどこから来るのですか? 99.9%の顧客が満たす必要があるという要件ではなく、ソリューションの副作用があることは素晴らしいことです。 ソリューション間で設計を選択するために、この影響の少ないディメンションを選択する理由がわかりません。
0.1%の顧客が別の次元の顧客の12%に悪影響を与える場合、最適化に基づいて設計を選択することはありません。 「割り当てなしを気にする」+「2つの値型を扱う」はエッジケースです。
サポートされている動作/機能の次元は、特に幅広い対象者とさまざまなユースケース向けの汎用の多用途データ構造を設計する場合に、はるかに重要であると思います。
この欲求はどこから来るのですか?
パフォーマンスを重視するシナリオでコアコレクションタイプを使用できるようにすることから。 ノードベースのソリューションはユースケースの100%をサポートすると言いますが、 List<T>
、 Dictionary<TKey, TValue>
、 HashSet<T>
などのように、すべてのエンキューが割り当てられる場合はそうではありません。 onは、すべてのAddに割り当てられた場合、多くの状況で使用できなくなります。
これらのメソッドの割り当てオーバーヘッドを気にするのはなぜ「0.1%」だけだと思いますか? そのデータはどこから来るのですか?
「割り当てなしを気にする」+「2つの値型を扱う」はエッジケースです
そうではない。 また、「2つの値型」だけではありません。 私が理解しているように、提案されたソリューションでは、a)関係するTに関係なく、すべてのエンキューに割り当てるか、b)要素タイプを既知の基本タイプから派生させる必要があります。余分な割り当ては避けてください。
@eiriktsarpalis
したがって、オプションを忘れないでください。リストのオプション1、2、および3に追加する実行可能なオプション4があると思いますが、これは妥協案です。
更新可能なデータ構造を必要としない、またはそもそも1つに最適化された実装から、88%または12%に利用可能な追加のパフォーマンスがあるため、オプション2および3を提供する方が良いと判断する場合があります。オプション4。しかし、別のオプションが存在することを忘れてはならないと思いました。
[または、これをより良いオプション1と見なし、1の説明を更新して、簿記は強制されないが、怠惰で、更新が使用される場合にのみ正しい同等の動作が必要であると言うことができると思います...]
@stephentoubそれはまさにあなたが言いたいことを簡単に言うことについて私が心に留めていたものです、ありがとう:)
これらのメソッドの割り当てオーバーヘッドを気にするのはなぜ0.1%だけだと思いますか? そのデータはどこから来るのですか?
直感から、つまり、「更新を実行する能力」よりも「追加の割り当てなし」を優先することがより重要であると考える同じソース。 少なくとも更新メカニズムについては、11〜12%のお客様がこの動作をサポートする必要があるというデータがあります。 リモートで近い顧客が「追加の割り当てなし」を気にすることはないと思います。
いずれの場合も、何らかの理由でメモリディメンションに固執することを選択し、他のディメンション、たとえば生の速度を忘れています。これは、優先アプローチのもう1つのトレードオフです。 「追加の割り当てなし」を提供するアレイベースの実装は、ノードベースの実装よりも遅くなります。 繰り返しますが、ここでは速度よりもメモリを優先するのは恣意的だと思います。
一歩下がって、顧客が何を望んでいるかに焦点を当てましょう。 私たちは、12%の顧客がデータ構造を使用できなくするかもしれないし、しないかもしれない設計上の選択を持っています。 これらをサポートしないことを選択する理由を提供することには、非常に注意する必要があると思います。
「追加の割り当てなし」を提供するアレイベースの実装は、ノードベースの実装よりも遅くなります。
その比較を実行するために使用している2つのC#実装と、その結論に到達するために使用されたベンチマークを共有してください。 理論的な論文は確かに価値がありますが、それらはパズルのほんの一部にすぎません。 より重要なことは、特定のプラットフォームと特定の実装の詳細を考慮に入れて、ゴムが道路に出会うときです。特定の実装と典型的な/予想されるデータセット/使用パターンを使用して特定のプラットフォームで検証できます。 あなたの主張が正しいことは十分にあり得ます。 また、そうではないかもしれません。 実装/データをよりよく理解するために見たいです。
その比較を実行するために使用している2つのC#実装と、その結論に達するために使用されたベンチマークを共有してください
これは有効なポイントです。私が引用する論文は、C ++での実装の比較とベンチマークのみを行っています。 さまざまなデータセットと使用パターンで複数のベンチマークを実施します。 これはC#に転送できると確信していますが、これを2倍にする必要があると思われる場合は、同僚にそのような調査を依頼するのが最善の策だと思います。
@pgolebiowski私はあなたの異議の性質をよりよく理解することに興味があります。 提案は2つの別々のタイプを提唱していますが、それはあなたの要件をカバーしていませんか?
- 12%のユースケースをサポートする実装であり、同等の要素への更新を許可することで他の88%をほぼ最適化し、更新メソッドが最初に呼び出されたときにそれらの更新を実行するために必要なルックアップテーブルを怠惰に構築するだけです(サブシーケンスの更新+削除で更新します)。 したがって、この機能を使用しないアプリのコストは低くなります。
私はおそらくそれをオプション1のパフォーマンス最適化として分類しますが、その特定のアプローチにはいくつかの問題があります。
@eiriktsarpalis一度だけO(n)、その後O(1)、つまりO(1)が償却されます。 また、一意性の検証を最初の更新まで延期できます。 しかし、多分これは過度に賢いです。 2つのクラスは説明が簡単です。
私はここ数日、2つの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;
}
}
最も基本的なアプリケーションの2つの実装を比較する単純なヒープソートベンチマークを作成しました。 比較のために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 | 比率| RatioSD | 第0世代| 第1世代| 第2世代| 割り当て済み|
| -------------- | ------ | -------------:| -----------: | -----------:| ------:| --------:| --------:| -------- :| --------:| ----------:|
| LinqSort | 30 | 1.439私たち| 0.0072 us | 0.0064 us | 1.00 | 0.00 | 0.0095 | -| -| 672 B |
| PriorityQueue | 30 | 1.450 us | 0.0085 us | 0.0079 us | 1.01 | 0.01 | -| -| -| -|
| PrioritySet | 30 | 2.778私たち| 0.0217 us | 0.0192 us | 1.93 | 0.02 | -| -| -| -|
| | | | | | | | | | | |
| LinqSort | 300 | 24.727私たち| 0.1032 us | 0.0915 us | 1.00 | 0.00 | 0.0305 | -| -| 3912 B |
| PriorityQueue | 300 | 29.510私たち| 0.0995 us | 0.0882 us | 1.19 | 0.01 | -| -| -| -|
| PrioritySet | 300 | 47.715私たち| 0.4455 us | 0.4168 us | 1.93 | 0.02 | -| -| -| -|
| | | | | | | | | | | |
| LinqSort | 3000 | 412.015私たち| 1.5495 us | 1.3736私たち| 1.00 | 0.00 | 0.4883 | -| -| 36312 B |
| PriorityQueue | 3000 | 491.722 us | 4.1463私たち| 3.8785私たち| 1.19 | 0.01 | -| -| -| -|
| PrioritySet | 3000 | 677.959私たち| 3.1996私たち| 2.4981私たち| 1.64 | 0.01 | -| -| -| -|
| | | | | | | | | | | |
| LinqSort | 30000 | 5,223.560 us | 11.9077私たち| 9.9434私たち| 1.00 | 0.00 | 93.7500 | 93.7500 | 93.7500 | 360910 B |
| PriorityQueue | 30000 | 5,688.625 us | 53.0746 us | 49.6460 us | 1.09 | 0.01 | -| -| -| 2 B |
| PrioritySet | 30000 | 8,124.306 us | 39.9498私たち| 37.3691私たち| 1.55 | 0.01 | -| -| -| 4 B |
予想されるように、要素の場所を追跡するオーバーヘッドにより、パフォーマンスが大幅に低下し、ベースラインの実装と比較して約40〜50%遅くなります。
私はすべての努力に感謝します、私はそれが多くの時間とエネルギーを要したのを見ます。
tl; dr:
(priority: ComputePriority(element), element)
をPQに格納するだけで、優先度取得関数は単純にtuple => tuple.priority
ます。私は、ロボット工学とゲームの両方に関連する高性能検索(モーションプランニング)と計算幾何学コード(スイープラインアルゴリズムなど)で作業しています。多くのカスタムの手動ロール優先キューを使用しています。 私が持っている他の一般的なユースケースは、更新可能な優先度が役に立たないTop-Kクエリです。
2つの実装(更新サポートがないかどうか)に関するいくつかのフィードバックが議論されています。
ネーミング:
パフォーマンス:
API:
Func<TElement, TPriority>
を取り込む方が望ましいです。 計算の優先順位が高い場合は、 (priority, element)
を挿入して、関数tuple => tuple.priority
を提供するだけです。そうは言っても、私のアルゴリズムのかなりの数について、優先度付きキューのアイテムには優先度が含まれており、それを2回(PQに1回、アイテムに1回)保存するのは非効率に聞こえます。 これは、多数のキーで注文している場合に特に当てはまります(OrderBy.ThenBy.ThenByと同様のユースケース)。 このAPIは、Insertが優先されるが、Peekがそれを返さない多くの不整合も解消します。
最後に、配列要素自体ではなく、配列のインデックスを優先度付きキューに挿入することがよくあることは注目に値します。 ただし、これはこれまでに説明したすべてのAPIでサポートされています。 例として、x軸の線で間隔の開始/終了を処理している場合、優先キューイベント(x, isStartElseEnd, intervalId)
あり、xで並べ替えてからisStartElseEndで並べ替える場合があります。 これは多くの場合、インデックスから計算されたデータにマップする他のデータ構造があるためです。
@pgolebiowski 3つのアプローチすべてのインスタンスを直接比較できるように、提案されたペアリングヒープの実装をベンチマークに自由に含めることができました。 結果は次のとおりです。
| 方法| サイズ| 平均| エラー| StdDev | 中央値| 第0世代| 第1世代| 第2世代| 割り当て済み|
| -------------- | -------- | -------------------:| ---- -------------:| -----------------:| ---------------- ---:| ----------:| ------:| ------:| -----------:|
| PriorityQueue | 10 | 774.7 ns | 3.30 ns | 3.08 ns | 773.2 ns | -| -| -| -|
| PrioritySet | 10 | 1,643.0 ns | 3.89 ns | 3.45 ns | 1,642.8 ns | -| -| -| -|
| ペアリングヒープ| 10 | 1,660.2 ns | 14.11 ns | 12.51 ns | 1,657.2 ns | 0.0134 | -| -| 960 B |
| PriorityQueue | 50 | 6,413.0 ns | 14.95 ns | 13.99 ns | 6,409.5 ns | -| -| -| -|
| PrioritySet | 50 | 12,193.1 ns | 35.41 ns | 29.57 ns | 12,188.3 ns | -| -| -| -|
| ペアリングヒープ| 50 | 13,955.8 ns | 193.36 ns | 180.87 ns | 13,989.2 ns | 0.0610 | -| -| 4800 B |
| PriorityQueue | 150 | 27,402.5 ns | 76.52 ns | 71.58 ns | 27,410.2 ns | -| -| -| -|
| PrioritySet | 150 | 48,485.8 ns | 160.22 ns | 149.87 ns | 48,476.3 ns | -| -| -| -|
| ペアリングヒープ| 150 | 56,951.2 ns | 190.52 ns | 168.89 ns | 56,953.6 ns | 0.1831 | -| -| 14400 B |
| PriorityQueue | 500 | 124,933.7 ns | 429.20 ns | 380.48 ns | 124,824.4 ns | -| -| -| -|
| PrioritySet | 500 | 206,310.0 ns | 433.97 ns | 338.81 ns | 206,319.0 ns | -| -| -| -|
| ペアリングヒープ| 500 | 229,423.9 ns | 3,213.33 ns | 2,848.53 ns | 230,398.7 ns | 0.4883 | -| -| 48000 B |
| PriorityQueue | 1000 | 284,481.8 ns | 475.91 ns | 445.16 ns | 284,445.6 ns | -| -| -| -|
| PrioritySet | 1000 | 454,989.4 ns | 3,712.11 ns | 3,472.31 ns | 455,354.0 ns | -| -| -| -|
| ペアリングヒープ| 1000 | 459,049.3 ns | 1,706.28 ns | 1,424.82 ns | 459,364.9 ns | 0.9766 | -| -| 96000 B |
| PriorityQueue | 10000 | 3,788,802.4 ns | 11,715.81 ns | 10,958.98 ns | 3,787,811.9 ns | -| -| -| 1 B |
| PrioritySet | 10000 | 5,963,100.4 ns | 26,669.04 ns | 22,269.86 ns | 5,950,915.5 ns | -| -| -| 2 B |
| ペアリングヒープ| 10000 | 6,789,719.0 ns | 134,453.01 ns | 265,397.13 ns | 6,918,392.9 ns | 7.8125 | -| -| 960002 B |
| PriorityQueue | 1000000 | 595,059,170.7 ns | 4,001,349.38 ns | 3,547,092.00 ns | 595,716,610.5 ns | -| -| -| 4376 B |
| PrioritySet | 1000000 | 1,592,037,780.9 ns | 13,925,896.05 ns | 12,344,944.12 ns | 1,591,051,886.5 ns | -| -| -| 288 B |
| ペアリングヒープ| 1000000 | 1,858,670,560.7 ns | 36,405,433.20 ns | 59,815,170.76 ns | 1,838,721,629.0 ns | 1000.0000 | -| -| 96000376 B |
〜上記に照らして、ベースラインアレイヒープとペアリングヒープアプローチの間にはまだ有効なトレードオフがあると思います〜。
編集:私のベンチマークのバグ修正後に結果を更新しました、@ VisualMelonに感謝します
@eiriktsarpalis PairingHeap
ベンチマークは、間違っていると思います。 Add
へのパラメーターは間違った方法です。 それらを交換するとき、それは別の話です: https :
(最初に実装したときとまったく同じことをしました)
これは、ペアリングヒープが高速または低速であることを意味するのではなく、提供されるデータの分散/順序に大きく依存しているように見えることに注意してください。
@eiriktsarpalis re:PrioritySetの有用性...
シナリオでは優先度の更新がないため、更新可能がヒープソートの速度が遅くなること以外は期待できません。 (ヒープソートの場合も、重複を保持したい可能性があります。セットは適切ではありません。)
PrioritySetが有用かどうかを確認するためのリトマス試験は、優先度の更新を使用するアルゴリズムのベンチマークである必要があります。同じアルゴリズムの非更新実装ではなく、重複する値をキューに入れ、キューから外すときに重複を無視します。
@VisualMelonに感謝します。提案された修正の後で、結果とコメントを更新しました。
むしろ、提供されたデータの分布/順序に大きく依存しているようです。
キューに入れられた優先順位が単調であったという事実から恩恵を受けたのではないかと思います。
PrioritySetが有用かどうかを確認するためのリトマス試験は、優先度の更新を使用するアルゴリズムのベンチマークである必要があります。同じアルゴリズムの非更新実装ではなく、重複する値をキューに入れ、キューから外すときに重複を無視します。
@TimLovellSmithここでの私の目標は、最も一般的なPriorityQueueアプリケーションのパフォーマンスを測定することでした。更新のパフォーマンスを測定するのではなく、更新がまったく必要ない場合の影響を確認したかったのです。 ただし、ペアリングヒープと「PrioritySet」の更新を比較する別のベンチマークを作成することは理にかなっている場合があります。
@miyu詳細なフィードバックをありがとう、それは非常にありがたいです!
@TimLovellSmith更新を使用する簡単なベンチマークを作成しました。
| 方法| サイズ| 平均| エラー| StdDev | 中央値| 第0世代| 第1世代| 第2世代| 割り当て済み|
| ------------ | -------- | ---------------:| ---------- -----:| ---------------:| ---------------:| -------:| ------:| ------:| -----------:|
| PrioritySet | 10 | 1.052 us | 0.0106 us | 0.0099 us | 1.055 us | -| -| -| -|
| ペアリングヒープ| 10 | 1.055 us | 0.0042 us | 0.0035 us | 1.055 us | 0.0057 | -| -| 480 B |
| PrioritySet | 50 | 7.394私たち| 0.0527 us | 0.0493 us | 7.380 us | -| -| -| -|
| ペアリングヒープ| 50 | 8.587私たち| 0.1678 us | 0.1570 us | 8.634私たち| 0.0305 | -| -| 2400 B |
| PrioritySet | 150 | 27.522私たち| 0.0459 us | 0.0359 us | 27.523私たち| -| -| -| -|
| ペアリングヒープ| 150 | 32.045 us | 0.1076 us | 0.1007 us | 32.019 us | 0.0610 | -| -| 7200 B |
| PrioritySet | 500 | 109.097私たち| 0.6548 us | 0.6125 us | 109.162私たち| -| -| -| -|
| ペアリングヒープ| 500 | 131.647私たち| 0.5401 us | 0.4510 us | 131.588私たち| 0.2441 | -| -| 24000 B |
| PrioritySet | 1000 | 238.184私たち| 1.0282 us | 0.9618 us | 238.457私たち| -| -| -| -|
| ペアリングヒープ| 1000 | 293.236 us | 0.9396 us | 0.8789 us | 293.257私たち| 0.4883 | -| -| 48000 B |
| PrioritySet | 10000 | 3,035.982 us | 12.2952私たち| 10.8994私たち| 3,036.985 us | -| -| -| 1 B |
| ペアリングヒープ| 10000 | 3,388.685 us | 16.0675 us | 38.1861私たち| 3,374.565 us | -| -| -| 480002 B |
| PrioritySet | 1000000 | 841,406.888 us | 16,788.4775 us | 15,703.9522 us | 840,888.389 us | -| -| -| 288 B |
| ペアリングヒープ| 1000000 | 989,966.501 us | 19,722.6687 us | 30,705.8191 us | 996,075.410 us | -| -| -| 48000448 B |
別のメモとして、安定性の欠如が人々のユースケースの問題(または非問題)であるという議論/フィードバックがありましたか?
安定性の欠如が人々のユースケースの問題(または非問題)であるという議論/フィードバックがありました
どの実装も安定性を保証しませんが、ユーザーが序数を挿入順序で拡張することによって安定性を得るのは非常に簡単なはずです。
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優先度キューがどのように見えるかを特定しようとしていたので、次のデータを調べました。
これにより、次のポイントが得られました。
今後、.NET6に対して次のアクションを実行することを提案します。
シンプルで、ユーザー要件の大部分に対応し、可能な限り効率的なSystem.Collections.Generic.PriorityQueue
クラスを導入します。 配列に裏打ちされたクォータナリヒープを使用し、優先度の更新をサポートしません。 実装のプロトタイプはここにあります。 API提案の詳細を示す別の問題をまもなく作成します。
効率的な優先度の更新をサポートするヒープの必要性を認識しているため、この要件に対応する特殊なクラスの導入に向けて引き続き取り組んでいきます。 私たちは、[プロトタイプのカップルを評価している1 、 2 、トレードオフの独自のセットをそれぞれ]。 設計を完成させるにはさらに作業が必要になるため、このタイプを後の段階で導入することをお勧めします。
現時点では、このスレッドの寄稿者、特に@pgolebiowskiと@TimLovellSmithに感謝します。 あなたのフィードバックは、私たちの設計プロセスを導く上で大きな役割を果たしました。 更新可能な優先度付きキューの設計を強化するにあたり、引き続きご意見をお待ちしております。
今後、.NET6に対して次のアクションを実行することを提案します。[...]
いいですね :)
シンプルで、ユーザー要件の大部分に対応し、可能な限り効率的な
System.Collections.Generic.PriorityQueue
クラスを導入します。 配列に裏打ちされたクォータナリヒープを使用し、優先度の更新をサポートしません。
コードベースの所有者がこの方向性を承認して希望していると判断した場合、そのビットのAPI設計を引き続き主導し、最終的な実装を提供できますか?
現時点では、このスレッドの寄稿者、特に@pgolebiowskiと@TimLovellSmithに感謝します。 あなたのフィードバックは、私たちの設計プロセスを導く上で大きな役割を果たしました。 更新可能な優先度付きキューの設計を強化するにあたり、引き続きご意見をお待ちしております。
それはかなりの旅でした:D
System.Collections.Generic.PriorityQueue<TElement, TPriority>
のAPIが承認されました。 優先度の更新をサポートする潜在的なヒープ実装についての会話を続けるために、別の問題を作成しました。
この号を締めくくります。ご協力ありがとうございました。
たぶん誰かがこの旅について書くことができます! 1つのAPIで全体で6年。 :)ギネスを獲得するチャンスはありますか?
最も参考になるコメント
ヒープデータ構造は、leetcodeを実行するために必須です
より多くのleetcode、より多くのc#コードインタビュー、つまりより多くのc#開発者。
より多くの開発者はより良いエコシステムを意味します。
より良いエコシステムとは、明日もc#でプログラムできるかどうかを意味します。
要約すると、これは機能であるだけでなく、将来でもあります。 そのため、問題には「将来」というラベルが付けられています。