web-dev-qa-db-fra.com

c # Ajout d'une méthode Remove (int index) à la classe .NET Queue

J'aimerais utiliser la classe de file d'attente générique telle que décrite dans le .NET Framework (3.5) , Mais il me faudra une méthode Remove (int index) pour supprimer des éléments de la file d'attente. Puis-je obtenir cette fonctionnalité avec une méthode d'extension? Quelqu'un veut-il me diriger dans la bonne direction?

24
Alex

Ce que vous voulez, c'est un List<T> dans lequel vous appelez toujours RemoveAt(0) lorsque vous voulez obtenir l'élément à partir de la Queue. En réalité, tout le reste est identique (appeler Add ajouterait un élément à la fin de la Queue).

22
casperOne

Combinant les suggestions de casperOne et de David Anderson au prochain niveau. La classe suivante hérite de List et masque les méthodes préjudiciables au concept FIFO tout en ajoutant les trois méthodes de file d'attente (Equeue, Dequeu, Peek).

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

Code de test:

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

    }
}
12
rollercodester

Voici comment supprimer un élément - spécifique de la file avec une ligne de Linq (il s'agit de recréer la file, MAIS faute d'une meilleure méthode ...)

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

Je sais que cela ne supprime pas par index , mais néanmoins, cela pourrait être utile (cette question a la cote dans Google pour "supprimer un élément spécifique d'une file d'attente c #", alors j'ai décidé d'ajouter cette réponse, désolé).

11
Alex

C'est une réponse assez tardive mais je l'écris pour les futurs lecteurs

List<T> est exactement ce dont vous avez besoin, mais il présente un gros inconvénient par rapport à Queue<T>: il est implémenté avec un tableau, alors Dequeue() est assez coûteux (en termes de temps) car tous les éléments doivent être décalés d’un pas en arrière avec Array.Copy. Même Queue<T> utilise un tableau mais avec deux index (pour head et tail).

Dans votre cas, vous avez également besoin de Removename __/RemoveAtet ses performances ne seront pas bonnes (pour la même raison: si vous ne supprimez pas de la liste, un autre tableau doit être alloué et les éléments copiés).

Une meilleure structure de données pour avoir rapidement Dequeuename __/Removetime est une liste chaînée (vous sacrifierez un peu - les performances de Enqueuemais en supposant que votre file d'attente contient un nombre égal de Enqueuename __/Dequeue, surtout quand sa taille augmentera).

Voyons un squelette simple pour son implémentation (je vais ignorer l'implémentation de IEnumerable<T>, IList<T> et d'autres méthodes d'assistance).

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

Pour une comparaison rapide:

 Liste de files d'attente LinkedList 
 Enqueue O (1)/O (n) * O (1)/O (n) * O (1) 
 Dequeue O(1) O(n) O (1) 
 Supprimer n/a O(n) O (n) 

* O(1) est typique cas mais parfois ce sera O(n) (lorsqu'un tableau interne doit être redimensionné).

Bien sûr, vous allez payer quelque chose pour ce que vous gagnez: l'utilisation de la mémoire est plus grande (en particulier pour les petits Tname__, ce qui sera super, ce sera super). La bonne implémentation (List<T> vs LinkedList<T>) doit être choisie avec soin en fonction de votre scénario d'utilisation. Vous pouvez également convertir ce code pour utiliser une seule liste chaînée afin de réduire de 50% la surcharge de mémoire.

7
Adriano Repetti

Quelqu'un développera probablement une meilleure solution, mais d'après ce que je vois, vous devrez retourner un nouvel objet Queue dans votre méthode Remove. Vous voudrez vérifier si l'index est en dehors des limites et si les commandes d'ajout d'éléments ont été mal classées, mais voici un exemple rapide et sale qui pourrait facilement être transformé en une extension.

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

En fait, cela défait tout le but de Queue et la classe que vous allez finir par trouver va violer la sémantique FIFO.

3
Anton Gogolev

Si la file d'attente est utilisée pour conserver l'ordre des éléments de la collection et si vous ne voulez pas que des éléments soient dupliqués, un SortedSet pourrait être ce que vous recherchez. La SortedSet agit beaucoup comme un List<T>, mais reste ordonnée. Idéal pour des choses comme des sélections déroulantes.

2
sfuqua

Bien qu'il n'y ait pas de méthode intégrée, vous ne devriez pas utiliser une structure List ou une autre structure, IFF RemoveAt n'est pas une opération fréquente.

Si vous mettez normalement en file d'attente et en file d'attente, mais que vous supprimez de temps en temps, vous devriez pouvoir vous permettre une reconstruction de la file d'attente lors de la suppression.

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) where T : class
{
    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]);
    }
}
2
Todd

La solution de David Anderson est probablement la meilleure mais a quelques frais généraux. Utilisez-vous des objets personnalisés dans la file d'attente? si oui, ajoutez un booléen comme annuler

Vérifiez auprès de vos employés qui traitent la file d'attente si ce booléen est défini, puis ignorez-le.

1
RvdK

Je ne pense pas que nous devrions utiliser List<T> pour émuler une file d'attente. Pour une file d'attente, les opérations de mise en file d'attente et de retrait de la file d'attente devraient être très performantes, ce qu'elles ne seraient pas avec un List<T>. Cependant, pour la méthode RemoveAt, il est acceptable d’être non performant, car ce n’est pas l’objet principal d’un Queue<T>.

Mon approche pour implémenter RemoveAt est O(n), mais la file d’attente conserve toujours une file d'attente largement O(1) (parfois le tableau interne doit être réaffecté, ce qui rend les opérations O(n)) et toujours O(1) dequeue.

Voici mon implémentation d'une méthode d'extension RemoveAt(int) pour un Queue<T>:

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 est défini comme suit:

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

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

Cette implémentation modifie également le Queue<T> réel plutôt que de renvoyer un nouveau Queue<T> (ce qui, je pense, est plus conforme aux autres implémentations de RemoveAt).

1
Lukazoid

Notez qu'avec une liste, vous pouvez rendre le processus de "suppression" plus efficace si vous ne supprimez pas réellement l'élément, mais simplement "le marquez" comme "supprimé". Oui, vous devez ajouter un peu de code pour expliquer comment vous l'avez fait, mais le bénéfice est l'efficacité.

Par exemple, supposons que vous ayez un List<string>. Ensuite, vous pouvez, par exemple, définir simplement cet élément à null et en finir avec lui.

0
Steve Greene