web-dev-qa-db-ja.com

要素の一意性を保証するキュー?

Java.util.Queueの実装、またはキューのように動作するGoogleコレクション内の何かを探していますが、キューの各要素が一意であることも確認しています。 (それ以上挿入しても効果はありません)

それは可能ですか、それとも手でやらなければなりませんか?

今のところ、LinkedList実装でQueueを使用しており、挿入前に一意性を確認しています。 (私はこれを行うためにサイドマップを使用し、キューの前後にサイドマップから要素を追加/削除します)。あまり好きではありません。

任意の入力を歓迎します。 Java.utilパッケージにない場合、それは悪い考えでしょうか?

60
Antoine Claval

LinkedHashSet ?その反復子は挿入順序を保持しますが、Setであるため、その要素は一意です。

そのドキュメントが言うように、

要素がre-insertedである場合、挿入順序はnot影響を受けることに注意してくださいセットに。

この「キュー」の先頭から要素を効率的に削除するには、イテレータを通過します。

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

これは私が知る限り存在しませんが、LinkedListSetを組み合わせて使用​​することで、かなり簡単に実装できます。

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

キュー内の項目を並べて一意に識別するキーを含む HashSet を維持したいと思うでしょう。次に、HashSetをチェックして、アイテムを追加する前にキューにあるかどうかを確認します。キューからアイテムを削除するときは、HashSetからもキーを削除するだけです。

4
tvanfosson

コースの一意性を確認するにはコストがかかります(空間または時間のいずれかで)。要素のコンパレータによってソートされたヒープを維持するPriorityQueueのようなものから作業することは興味深いかもしれません。これを活用して、サイドマップを維持することなく、より効率的に(O(log n))存在を確認できる場合があります。

キューを一意性チェッカーでラップしたい場合は、Google Collections ForwardingQueue を使用してそのようなものを作成することを強くお勧めします。

4
Alex Miller

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

答えるのが少し遅れましたが、ArrayDequeを使用して同様の問題を解決し、必要なaddメソッドをオーバーライドしました。

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

これは非常に良い質問です。既存の簡単なソリューションはありません。しばらく前に書いたコードを掘り下げて、これを実行しようとし、戻ってこの回答を編集します。

EDIT:戻ってきました。確かに、並行性が必要ない場合は、キューとセットを別々に維持する方が良いでしょう。私がやっていたことについては、並行性が目標でしたが、その制約を考えると思いつく最高の解決策は問題でした。基本的に、ConcurrentHashMapを使用しているため、キューから「ヘッド」要素を削除するほど(キューを処理するための基本的なこと)、ハッシュテーブルは時間の経過とともに不均衡になります。私はまだこのコードをあなたと共有できますが、本当にあなたがそれを望んでいるとは思いません。

EDIT:並行性が必要な場合、私はこの答えを与えました: Concurrent Set Queue

1

残念ながら存在しません。このようなキューが必要だったので、Java.util.concurrent.LinkedBlockingQueueに触発されたセットに裏打ちされたブロッキングキューを開発しました。

ここで見つけることができます:

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

例:

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

Mavenで使用できます:

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