web-dev-qa-db-fra.com

Une file d'attente qui assure l'unicité des éléments?

Je recherche une implémentation de Java.util.Queue ou quelque chose dans la collection Google qui se comporte comme une file d'attente, mais s'assure également que chaque élément de la file d'attente est unique. (toute nouvelle insertion n'aura aucun effet)

Est-ce possible, ou devrais-je le faire à la main?

Pour l'instant, j'utilise une file d'attente, avec une implémentation LinkedList, et je vérifie l'unicité avant l'insertion. (J'utilise une carte latérale pour ce faire, ajouter/supprimer un élément de la carte latérale avant/après la file d'attente). Je n'aime pas trop ça.

Toute contribution est la bienvenue. Si ce n'est pas dans le package Java.util, alors c'est peut-être une mauvaise idée?

60
Antoine Claval

Que diriez-vous d'un LinkedHashSet ? Son itérateur préserve l'ordre d'insertion, mais comme il s'agit d'un Set, ses éléments sont uniques.

Comme le dit sa documentation,

Notez que l'ordre d'insertion n'est pas affecté si un élément est réinséré dans l'ensemble.

Afin de supprimer efficacement les éléments de la tête de cette "file d'attente", passez par son itérateur:

Iterator<?> i = queue.iterator();
...
Object next = i.next();
i.remove();
46
erickson

Cela n'existe pas pour autant que je sache mais serait assez simple à implémenter en utilisant un LinkedList en conjonction avec un Set:

/**
 * Thread unsafe implementation of UniqueQueue.
 */
public class UniqueQueue<T> implements Queue<T> {
  private final Queue<T> queue = new LinkedList<T>();
  private final Set<T> set = new HashSet<T>();

  public boolean add(T t) {
    // Only add element to queue if the set does not contain the specified element.
    if (set.add(t)) {
      queue.add(t);
    }

    return true; // Must always return true as per API def.
  }

  public T remove() throws NoSuchElementException {
    T ret = queue.remove();
    set.remove(ret);
    return ret;
  }

  // TODO: Implement other Queue methods.
}
20
Adamski

Je serais tenté de conserver un HashSet contenant une clé qui identifie de manière unique les éléments de la file d'attente côte à côte avec elle. Ensuite, vérifiez simplement le HashSet pour voir si l'élément est dans la file d'attente avant de l'ajouter. Lorsque vous supprimez un élément de la file d'attente, il vous suffit également de supprimer la clé du HashSet.

4
tvanfosson

La vérification de l'unicité a bien sûr un coût (dans l'espace ou dans le temps). On dirait qu'il pourrait être intéressant de travailler à partir de quelque chose comme une PriorityQueue qui maintiendra un tas trié par Comparateur des éléments. Vous pourrez peut-être en tirer parti pour vérifier plus efficacement (O (log n)) l'existence sans maintenir une carte latérale.

Si vous souhaitez envelopper une file d'attente avec un vérificateur d'unicité, je vous recommande fortement d'utiliser les collections Google ForwardingQueue pour créer une telle chose.

4
Alex Miller

Juste pour compléter la réponse d'Adamski:

/**
 * A queue that keeps each element only once. 
 * If you try to add an element that already exists - nothing will happen.
 * 
 * @author Adamski http://stackoverflow.com/a/2319156/827927
 * @NotThreadSafe
 */
public class UniqueQueue<T> implements Queue<T> {

private final Queue<T> queue = new LinkedList<T>();
private final Set<T> set = new HashSet<T>();

@Override public boolean add(T t) {
    // Only add element to queue if the set does not contain the specified element.
    if (set.add(t))
        queue.add(t);
    return true; // Must always return true as per API def.
}

@Override public boolean addAll(Collection<? extends T> arg0) {
    boolean ret = false;
    for (T t: arg0)
        if (set.add(t)) {
            queue.add(t);
            ret = true;
        }
    return ret;
}

@Override public T remove() throws NoSuchElementException {
    T ret = queue.remove();
    set.remove(ret);
    return ret;
}

@Override public boolean remove(Object arg0) {
    boolean ret = queue.remove(arg0);
    set.remove(arg0);
    return ret;
}

@Override public boolean removeAll(Collection<?> arg0) {
    boolean ret = queue.removeAll(arg0);
    set.removeAll(arg0);
    return ret;
}

@Override public void clear() {
    set.clear();
    queue.clear();
}

@Override public boolean contains(Object arg0) {
    return set.contains(arg0);
}

@Override public boolean containsAll(Collection<?> arg0) {
    return set.containsAll(arg0);
}

@Override public boolean isEmpty() {
    return set.isEmpty();
}

@Override public Iterator<T> iterator() {
    return queue.iterator();
}

@Override public boolean retainAll(Collection<?> arg0) {
    throw new UnsupportedOperationException();
}

@Override public int size() {
    return queue.size();
}

@Override public Object[] toArray() {
    return queue.toArray();
}

@Override public <T> T[] toArray(T[] arg0) {
    return queue.toArray(arg0);
}

@Override public T element() {
    return queue.element();
}

@Override public boolean offer(T e) {
    return queue.offer(e);
}

@Override public T peek() {
    return queue.peek();
}

@Override public T poll() {
    return queue.poll();
}
}
4

Je suis un peu en retard pour répondre, mais j'ai fini par résoudre un problème similaire en utilisant un ArrayDeque et en remplaçant la méthode d'ajout dont j'avais besoin.

    Deque<Long> myQueue = new ArrayDeque<Long>() {
        @Override
        public boolean add(Long e) { return !this.contains(e) && super.add(e);}
    };
1
Tom

C'est une très bonne question. Il n'y a pas de solution simple existante. Je vais déterrer du code que j'ai écrit il y a quelque temps et qui a tenté de le faire, puis je reviendrai et modifierai cette réponse.

EDIT: Je suis de retour. Vraiment, si vous n'avez pas besoin de simultanéité, vous feriez mieux de maintenir une file d'attente et un ensemble séparément. Pour ce que je faisais, la concurrence était un objectif, mais la meilleure solution que je pouvais trouver étant donné que la contrainte était problématique; Fondamentalement, comme il utilisait un ConcurrentHashMap, plus vous supprimiez l'élément "head" de la file d'attente (chose de base à faire avec une file d'attente), plus la table de hachage deviendrait déséquilibrée au fil du temps. Je peux toujours partager ce code avec vous, mais je doute que vous le vouliez vraiment.

EDIT: Pour le cas où la simultanéité est requise, j'ai donné cette réponse: Concurrent Set Queue

1
Kevin Bourrillion

Malheureusement, cela n'existe pas. Depuis que j'avais besoin d'une telle file d'attente, j'ai développé une file d'attente de blocage soutenue par un ensemble, inspirée par Java.util.concurrent.LinkedBlockingQueue .

Vous pouvez le trouver ici :

https://github.com/bvanalderweireldt/concurrent-unique-queue

Exemple :

final BlockingQueue<Integer> queue = new ConcurrentSetBlockingQueue<>(1);
queue.offer(new Integer(1)); //True
queue.offer(new Integer(1)); //False

Vous pouvez l'utiliser avec Maven:

<dependency>
  <groupId>com.hybhub</groupId>
  <artifactId>concurrent-util</artifactId>
  <version>0.1</version>
</dependency>