web-dev-qa-db-ja.com

c#Remove(int index)メソッドを.NET Queueクラスに追加する

.NETフレームワーク(3.5)で説明されている汎用キュークラスを使用したいのですが、アイテムをキューから削除するには、Remove(int index)メソッドが必要です。拡張機能メソッドでこの機能を実現できますか?誰かが私を正しい方向に向けることを気にしますか?

24
Alex

欲しいのは _List<T>_ です。ここで、Queueからアイテムを取得する場合は常に RemoveAt(0) を呼び出します。それ以外はすべて同じです(Addを呼び出すと、Queueの末尾に項目が追加されます)。

21
casperOne

CasperOneとDavid Andersonの両方の提案を次のレベルに結合します。次のクラスは、Listを継承し、3つのQueueメソッド(Equeue、Dequeu、Peek)を追加するときにFIFOの概念に有害となるメソッドを非表示にします。

public class ListQueue<T> : List<T>
{
    new public void Add(T item) { throw new NotSupportedException(); }
    new public void AddRange(IEnumerable<T> collection) { throw new NotSupportedException(); }
    new public void Insert(int index, T item) { throw new NotSupportedException(); }
    new public void InsertRange(int index, IEnumerable<T> collection) { throw new NotSupportedException(); }
    new public void Reverse() { throw new NotSupportedException(); }
    new public void Reverse(int index, int count) { throw new NotSupportedException(); }
    new public void Sort() { throw new NotSupportedException(); }
    new public void Sort(Comparison<T> comparison) { throw new NotSupportedException(); }
    new public void Sort(IComparer<T> comparer) { throw new NotSupportedException(); }
    new public void Sort(int index, int count, IComparer<T> comparer) { throw new NotSupportedException(); }

    public void Enqueue(T item)
    {
        base.Add(item);
    }

    public T Dequeue()
    {
        var t = base[0]; 
        base.RemoveAt(0);
        return t;
    }

    public T Peek()
    {
        return base[0];
    }
}

テストコード:

class Program
{
    static void Main(string[] args)
    {
        ListQueue<string> queue = new ListQueue<string>();

        Console.WriteLine("Item count in ListQueue: {0}", queue.Count);
        Console.WriteLine();

        for (int i = 1; i <= 10; i++)
        {
            var text = String.Format("Test{0}", i);
            queue.Enqueue(text);
            Console.WriteLine("Just enqueued: {0}", text);
        }

        Console.WriteLine();
        Console.WriteLine("Item count in ListQueue: {0}", queue.Count);
        Console.WriteLine();

        var peekText = queue.Peek();
        Console.WriteLine("Just peeked at: {0}", peekText);
        Console.WriteLine();

        var textToRemove = "Test5";
        queue.Remove(textToRemove);
        Console.WriteLine("Just removed: {0}", textToRemove);
        Console.WriteLine();

        var queueCount = queue.Count;
        for (int i = 0; i < queueCount; i++)
        {
            var text = queue.Dequeue();
            Console.WriteLine("Just dequeued: {0}", text);
        }

        Console.WriteLine();
        Console.WriteLine("Item count in ListQueue: {0}", queue.Count);

        Console.WriteLine();
        Console.WriteLine("Now try to ADD an item...should cause an exception.");
        queue.Add("shouldFail");

    }
}
13
rollercodester

Linqの1行で特定のアイテムをキューから削除する方法を次に示します(キューを再作成していますが、より優れたメソッドがないためです...)

//replace "<string>" with your actual underlying type
myqueue = new Queue<string>(myqueue.Where(s => s != itemToBeRemoved));

私はそれが削除されていないことを知っていますインデックスによって、それでも、誰かがこれが役立つと思うかもしれません(この質問はGoogleで「特定のアイテムをC#キューから削除する」とランク付けされているため、この回答を追加することにしました

11
Alex

かなり遅い答えですが、将来の読者のために書きます

_List<T>_はまさに必要なものですが、_Queue<T>_と比較すると大きなデメリットがあります。それは配列で実装されているため、Dequeue()はすべてのアイテムが(時間の観点から)かなり拡張的です_Array.Copy_で1ステップ後ろにシフトする必要があります。 _Queue<T>_でも配列を使用しますが、2つのインデックス(ヘッドとテール)を使用します。

あなたのケースではRemove/RemoveAtも必要であり、そのパフォーマンスは良くありません(同じ理由で:リストの末尾から削除しない場合は、別の配列を割り当てて項目を追加する必要があります)コピーされた)。

Dequeue/Removeの時間を短縮するためのより良いデータ構造は、リンクされたリストです(Enqueueのパフォーマンスを少し犠牲にしますが、キューに等しいEnqueue/Dequeueオペレーションの数により、特にサイズが大きくなる場合にパフォーマンスが大幅に向上します。

その実装の簡単なスケルトンを見てみましょう(_IEnumerable<T>_、_IList<T>_およびその他のヘルパーメソッドの実装はスキップします)。

_class LinkedQueue<T>
{
    public int Count
    {
        get { return _items.Count; }
    }

    public void Enqueue(T item)
    {
        _items.AddLast(item);
    }

    public T Dequeue()
    {
        if (_items.First == null)
           throw new InvalidOperationException("...");

        var item = _items.First.Value;
        _items.RemoveFirst();

        return item;
    }

    public void Remove(T item)
    {
        _items.Remove(item);
    }

    public void RemoveAt(int index)
    {
        Remove(_items.Skip(index).First());
    }

    private LinkedList<T> _items = new LinkedList<T>();
}
_

簡単な比較:

キューリストLinkedList 
エンキューO(1)/ O(n)* O(1)/ O(n)* O(1)
デキューO(1) O(n) O(1)
 n/aを削除O(n) O(n) 

* O(1) is typical case but sometimes it's will O(n)(内部配列のサイズを変更する必要がある場合)。

もちろん、あなたはあなたが得るもののために何かを支払うでしょう:メモリ使用量はより大きくなります(特に、小さなTのオーバーヘッドは素晴らしいでしょう)。適切な実装(_List<T>_ vs _LinkedList<T>_)は、使用シナリオに従って慎重に選択する必要があります。また、単一のリンクリストを使用するようにコードを変換して、メモリオーバーヘッドを50%削減することもできます。

7
Adriano Repetti

誰かがおそらくより良いソリューションを開発するでしょうが、私が見るところからは、Removeメソッドで新しいQueueオブジェクトを返す必要があります。インデックスが範囲外であるかどうかを確認する必要があります。追加された項目の順序が間違っている可能性がありますが、これは非常に簡単に拡張機能にできる簡単な例です。

public class MyQueue<T> : Queue<T> {
    public MyQueue() 
        : base() {
        // Default constructor
    }

    public MyQueue(Int32 capacity)
        : base(capacity) {
        // Default constructor
    }

    /// <summary>
    /// Removes the item at the specified index and returns a new Queue
    /// </summary>
    public MyQueue<T> RemoveAt(Int32 index) {
        MyQueue<T> retVal = new MyQueue<T>(Count - 1);

        for (Int32 i = 0; i < this.Count - 1; i++) {
            if (i != index) {
                retVal.Enqueue(this.ElementAt(i));
            }
        }

        return retVal;
    }
}
6
David Anderson

組み込みの方法はありませんが、List構造やその他の構造を使用しないでください。IFFRemoveAtは頻繁に実行される操作ではありません。

通常はエンキューおよびデキューを行っているが、たまにしか削除しない場合は、削除時にキューを再構築する余裕が必要です。

public static void Remove<T>(this Queue<T> queue, T itemToRemove) where T : class
{
    var list = queue.ToList(); //Needs to be copy, so we can clear the queue
    queue.Clear();
    foreach (var item in list)
    {
        if (item == itemToRemove)
            continue;

        queue.Enqueue(item);
    }
}

public static void RemoveAt<T>(this Queue<T> queue, int itemIndex) 
{
    var list = queue.ToList(); //Needs to be copy, so we can clear the queue
    queue.Clear();

    for (int i = 0; i < list.Count; i++)
    {
        if (i == itemIndex)
            continue;

        queue.Enqueue(list[i]);
    }
}

次のアプローチは、より少ないメモリを使用し、GCを少なくして、より効率的になる可能性があります。

public static void RemoveAt<T>(this Queue<T> queue, int itemIndex)
{
    var cycleAmount = queue.Count;

    for (int i = 0; i < cycleAmount; i++)
    {
        T item = queue.Dequeue();
        if (i == itemIndex)
            continue;

        queue.Enqueue(item);
    }
}
5
Todd

キューがコレクション内のアイテムの順序を維持するために使用されており、重複するアイテムがない場合は、 SortedSet が必要な場合があります。 SortedSetList<T>のように機能しますが、順序付けされたままです。ドロップダウンセレクションなどに最適です。

2
sfuqua

実際、これはQueueの目的全体を無効にし、最終的にあなたが思いつくクラスは、FIFOセマンティクスに完全に違反します。

2
Anton Gogolev

キューをエミュレートするために_List<T>_を使用する必要があるとは思いません。キューの場合、エンキュー操作とデキュー操作は非常に高いパフォーマンスが必要ですが、_List<T>_を使用する場合はそうではありません。ただし、RemoveAtメソッドは、_Queue<T>_の主な目的ではないため、非パフォーマンスであっても問題ありません。

RemoveAtの実装における私のアプローチはO(n)ですが、キューは依然として大部分を維持していますO(1)エンキュー(場合によっては内部配列)操作を再割り当てする必要がありますO(n))および常にO(1)デキューします。

以下は、_Queue<T>_のRemoveAt(int)拡張メソッドの実装です。

_public static void RemoveAt<T>(this Queue<T> queue, int index)
{
    Contract.Requires(queue != null);
    Contract.Requires(index >= 0);
    Contract.Requires(index < queue.Count);

    var i = 0;

    // Move all the items before the one to remove to the back
    for (; i < index; ++i)
    {
        queue.MoveHeadToTail();
    }

    // Remove the item at the index
    queue.Dequeue();

    // Move all subsequent items to the tail end of the queue.
    var queueCount = queue.Count;
    for (; i < queueCount; ++i)
    {
        queue.MoveHeadToTail();
    }
}
_

ここで、MoveHeadToTailは次のように定義されています。

_private static void MoveHeadToTail<T>(this Queue<T> queue)
{
    Contract.Requires(queue != null);

    var dequed = queue.Dequeue();
    queue.Enqueue(dequed);
}
_

この実装は、新しい_Queue<T>_を返すのではなく、実際の_Queue<T>_も変更します(これは、他のRemoveAt実装とより調和していると思います)。

2
Lukazoid

David Andersonのソリューション はおそらく最高ですが、オーバーヘッドが多少あります。キューでカスタムオブジェクトを使用していますか?もしそうなら、キャンセルのようなブール値を追加します

ブール値が設定されているかどうかをキューを処理するワーカーに確認し、スキップします。

1
RvdK

リストを使用すると、アイテムを実際に削除せず、単に「削除済み」として「マーク」するだけで、「削除」プロセスをより効率的にできることに注意してください。はい、あなたはそれをどのように行ったかに対処するために少しのコードを追加する必要がありますが、見返りは効率です。

ちょうど1つの例として-List<string>。次に、たとえば、その特定の項目をnullに設定して、それで処理を完了できます。

0
Steve Greene