删除“第一个”集合中的对象

发布于 2024-11-03 13:46:17 字数 906 浏览 0 评论 0原文

在某些情况下,我需要逐出 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 行代码来完成我可以用 1 行代码完成的事情SortedSet (由于其他原因,SortedSet 在这里不是一个选项):

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

那么有没有一种更简洁的方法来执行此操作,无需:

  • 更改Set 实现,还是
  • 编写静态实用方法?

任何利用 Guava 的解决方案都可以。


我完全意识到集合没有固有的顺序。我询问如何删除迭代顺序定义的第一个条目。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

初见 2024-11-10 13:46:17

LinkedHashSet 是 LinkedHashMap 的包装器,它支持简单的“删除最旧的”策略。要将其用作集合,您可以执行以下操作

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

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;
    }
});
芯好空 2024-11-10 13:46:17
    if (!mySet.isEmpty())
      mySet.remove(mySet.iterator().next());

好像不到3行。

当然,如果您的集合由多个线程共享,您必须围绕它进行同步。

    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.

依 靠 2024-11-10 13:46:17

如果您确实需要在代码中的多个位置执行此操作,只需编写一个静态方法即可。

提出的其他解决方案通常速度较慢,因为它们意味着调用 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;
}

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;
}
小糖芽 2024-11-10 13:46:17

对于番石榴:

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 行,但对于下面的代码是不可见的使用它。由于这实际上是集合的特定行为,因此将其包含在集合中是合乎逻辑的。

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.

后来的我们 2024-11-10 13:46:17

我认为你这样做的方式很好。这是您经常做的事情,值得寻找更短的方法吗?您可以使用 Guava 做基本相同的事情,如下所示:

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

这增加了包装集合及其迭代器以进行限制的小开销,然后调用 alwaysTrue() 谓词一次......似乎并不特别不过对我来说值得。

编辑:要将我在评论中所说的内容放入答案中,您可以创建一个 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;
          }
        });
      }
    });

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;
          }
        });
      }
    });
缱绻入梦 2024-11-10 13:46:17

快速而肮脏的单行解决方案: mySet.remove(mySet.toArray(new Foo[mySet.size()])[0]) ;)

但是,我仍然会选择迭代器解决方案,因为这会更具可读性并且也应该更快。

编辑:我会选择迈克·塞缪尔的解决方案。 :)

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. :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文