Удаление" первого " объекта из набора
в определенных ситуациях мне нужно выселить самый старый элемент в 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
или - статические служебный метод?
любые решения, использующиегуавы хорошо.
я полностью осознаю, что наборы не присуща заказ. Я спрашиваю об удалении первой записи, определенной порядком итерации.
6 ответов
LinkedHashSet-это оболочка для LinkedHashMap, которая поддерживает простую политику" удалить старейшую". Чтобы использовать его как набор, Вы можете сделать
Set<String> set = Collections.newSetFromMap(new LinkedHashMap<String, Boolean>(){
protected boolean removeEldestEntry(Map.Entry<String, Boolean> eldest) {
return size() > MAX_ENTRIES;
}
});
if (!mySet.isEmpty())
mySet.remove(mySet.iterator.next());
кажется, меньше 3 строк.
вы должны синхронизировать вокруг него, конечно, если ваш набор совместно используемых несколькими потоками.
Если вам действительно нужно сделать это в нескольких местах вашего кода, просто напишите статический метод.
другие предлагаемые решения часто медленнее, поскольку они подразумевают вызов 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 (!set.isEmpty() && set.size() >= MAX_SET_SIZE) {
set.remove(Iterables.get(set, 0));
}
Я предлагаю альтернативный подход. Да, это меняет реализацию, но не кардинально: extend 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 строк, но он невидим для кода, который его использует. И поскольку это на самом деле конкретное поведение множества, логично иметь его внутри множества.
Я думаю, что то, как вы это делаете, прекрасно. Это то, что вы делаете достаточно часто, чтобы стоило найти более короткий путь? Вы могли бы сделать в основном то же самое с гуавой, как это:
Iterables.removeIf(Iterables.limit(mySet, 1), Predicates.alwaysTrue());
это добавляет небольшие накладные расходы на обертывание набора и его итератора для ограничения, а затем вызова alwaysTrue()
после сказуемого... хотя, по-моему, оно того не стоит.
Edit: чтобы поместить то, что я сказал в комментарии в ответ, вы можете создать 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;
}
});
}
});
быстрое и грязное решение: mySet.remove(mySet.toArray(new Foo[mySet.size()])[0])
;)
однако я бы все равно пошел на решение итератора, так как это было бы более читаемым и также должно быть быстрее.
Edit: я бы пошел на решение Майка Сэмюэля. :)