Removing the “first” object from a Set

巧了我就是萌 提交于 2020-01-12 02:53:06

问题


Under certain situations, I need to evict the oldest element in a Java Set. The set is implemented using a LinkedHashSet, which makes this simple: just get rid of the first element returned by the set's iterator:

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

This is ugly: 3 lines to do something I could do with 1 line if I was using a SortedSet (for other reasons, a SortedSet is not an option here):

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

So is there a cleaner way of doing this, without:

  • changing the Set implementation, or
  • writing a static utility method?

Any solutions leveraging Guava are fine.


I am fully aware that sets do not have inherent ordering. I'm asking about removing the first entry as defined by iteration order.


回答1:


LinkedHashSet is a wrapper for LinkedHashMap which supports a simple "remove oldest" policy. To use it as a Set you can do

Set<String> set = Collections.newSetFromMap(new LinkedHashMap<String, Boolean>(){
    protected boolean removeEldestEntry(Map.Entry<String, Boolean> eldest) {
        return size() > MAX_ENTRIES;
    }
});



回答2:


if (!mySet.isEmpty())
  mySet.remove(mySet.iterator.next());

seems to be less than 3 lines.

You have to synchronize around it of course if your set is shared by multiple threads.




回答3:


If you really need to do this at several places in your code, just write a static method.

The other solutions proposed are often slower since they imply calling the Set.remove(Object) method instead of the Iterator.remove() method.

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



回答4:


With guava:

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

I will also suggest an alternative approach. Yes, it it changing the implementation, but not drastically: extend LinkedHashSet and have that condition in the add method:

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

It's still 5 line, but it is invisible to the code that's using it. And since this is actually a specific behaviour of the set, it is logical to have it within the set.




回答5:


I think the way you're doing it is fine. Is this something you do often enough to be worth finding a shorter way? You could do basically the same thing with Guava like this:

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

That adds the small overhead of wrapping the set and its iterator for limiting and then calling the alwaysTrue() predicate once... doesn't seem especially worth it to me though.

Edit: To put what I said in a comment in an answer, you could create a SetMultimap that automatically restricts the number of values it can have per key like this:

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



回答6:


Quick and dirty one-line solution: mySet.remove(mySet.toArray(new Foo[mySet.size()])[0]) ;)

However, I'd still go for the iterator solution, since this would be more readable and should also be faster.

Edit: I'd go for Mike Samuel's solution. :)



来源:https://stackoverflow.com/questions/5792596/removing-the-first-object-from-a-set

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!