web-dev-qa-db-ja.com

セットから「最初の」オブジェクトを削除する

特定の状況下では、Java Setの最も古い要素を削除する必要があります。セットは LinkedHashSet を使用して実装されます。これにより、これが簡単になります。セットのイテレータによって返される最初の要素を削除するだけです。

Set<Foo> mySet = new LinkedHashSet<Foo>();
// do stuff...
if (mySet.size() >= MAX_SET_SIZE)
{
    Iterator<Foo> iter = mySet.iterator();
    iter.next();
    iter.remove();
}

これは醜いです:私が何かをするための3行couldSortedSetを使用していた場合、1行で行います(他の理由で、ここではSortedSetはオプションではありません)::

if (/*stuff*/)
{
    mySet.remove(mySet.first());
}

それで、これを行うためのよりクリーンな方法があります、なし:

  • Set実装の変更、または
  • 静的ユーティリティメソッドを作成しますか?

Guava を活用するソリューションは問題ありません。


セットには固有の順序がないことを十分に認識しています。反復順序で定義されている最初のエントリを削除することについて質問しています。

19
Matt Ball

LinkedHashSetは、単純な「最も古いものを削除する」ポリシーをサポートするLinkedHashMapのラッパーです。セットとして使用するには、

Set<String> set = Collections.newSetFromMap(new LinkedHashMap<String, Boolean>(){
    protected boolean removeEldestEntry(Map.Entry<String, Boolean> eldest) {
        return size() > MAX_ENTRIES;
    }
});
31
Peter Lawrey
if (!mySet.isEmpty())
  mySet.remove(mySet.iterator.next());

3行未満のようです。

もちろん、セットが複数のスレッドで共有されている場合は、その周りで同期する必要があります。

25
Mike Samuel

コードのいくつかの場所でこれを本当に行う必要がある場合は、静的メソッドを作成するだけです。

提案されている他のソリューションは、Set.remove(Object)メソッドの代わりにIterator.remove()メソッドを呼び出すことを意味するため、多くの場合遅くなります。

@Nullable
public static <T> T removeFirst(Collection<? extends T> c) {
  Iterator<? extends T> it = c.iterator();
  if (!it.hasNext()) { return null; }
  T removed = it.next();
  it.remove();
  return removed;
}
8
Laurent Pireyn

グアバ付き:

if (!set.isEmpty() && set.size() >= MAX_SET_SIZE) {
    set.remove(Iterables.get(set, 0));
}

別のアプローチも提案します。はい、それは実装を変更しますが、大幅ではありません:LinkedHashSetを拡張し、addメソッドにその条件を設定します。

public LimitedLinkedHashSet<E> extends LinkedHashSet<E> {
    public void add(E element) {
         super.add(element);
         // your 5-line logic from above or my solution with guava
    }
}

まだ5行ですが、それを使用しているコードからは見えません。また、これは実際にはセットの特定の動作であるため、セット内に含めるのが論理的です。

3
Bozho

私はあなたがそれをしている方法は良いと思います。これは、もっと短い方法を見つける価値があるほど頻繁に行うことですか?あなたはこのようにグアバで基本的に同じことをすることができます:

_Iterables.removeIf(Iterables.limit(mySet, 1), Predicates.alwaysTrue());
_

これにより、セットとそのイテレータをラップして制限し、alwaysTrue()述語を1回呼び出すという小さなオーバーヘッドが追加されます...しかし、私には特に価値がないようです。

編集:回答のコメントに私が言ったことを入れるには、次のようにキーごとに持つことができる値の数を自動的に制限するSetMultimapを作成できます。

_SetMultimap<K, V> multimap = Multimaps.newSetMultimap(map,
    new Supplier<Set<V>>() {
      public Set<V> get() {
        return Sets.newSetFromMap(new LinkedHashMap<V, Boolean>() {
          @Override protected boolean removeEldestEntry(Entry<K, V> eldestEntry) {
            return size() > MAX_SIZE;
          }
        });
      }
    });
_
2
ColinD

迅速で汚い1行の解決策:mySet.remove(mySet.toArray(new Foo[mySet.size()])[0]);)

ただし、これは読みやすく、高速である必要があるため、イテレータソリューションを使用します。

編集:私はマイクサミュエルの解決策に行きます。 :)

0
Thomas