Ответ 1
Вы можете использовать SortedSet, например. TreeSet с пользовательским компаратором и удалите наименьшее, когда размер достигнет N.
Похожие вопросы:
У меня есть очень большой набор данных (более 5 миллионов элементов), и мне нужно получить N наибольших элементов. Самый естественный способ сделать это - использовать очередь кучи/приоритета хранить только верхние N элементов. Существует несколько хороших реализаций очереди приоритетов для JVM (Scala/Java), а именно:
Первые 2 хороши, но они сохраняют все элементы, которые в моем случае дают критические издержки памяти. В-третьих (реализация Lucene) не имеет такого недостатка, но, как я вижу из документации, он также не поддерживает пользовательский компаратор, что делает его бесполезным для меня.
Итак, мой вопрос: существует ли PriorityQueue
реализация с фиксированной емкостью и специализированным компаратором?
UPD. Наконец, я создал свою собственную реализацию на основе ответа Питера:
public class FixedSizePriorityQueue<E> extends TreeSet<E> {
private int elementsLeft;
public FixedSizePriorityQueue(int maxSize) {
super(new NaturalComparator());
this.elementsLeft = maxSize;
}
public FixedSizePriorityQueue(int maxSize, Comparator<E> comparator) {
super(comparator);
this.elementsLeft = maxSize;
}
/**
* @return true if element was added, false otherwise
* */
@Override
public boolean add(E e) {
if (elementsLeft == 0 && size() == 0) {
// max size was initiated to zero => just return false
return false;
} else if (elementsLeft > 0) {
// queue isn't full => add element and decrement elementsLeft
boolean added = super.add(e);
if (added) {
elementsLeft--;
}
return added;
} else {
// there is already 1 or more elements => compare to the least
int compared = super.comparator().compare(e, this.first());
if (compared == 1) {
// new element is larger than the least in queue => pull the least and add new one to queue
pollFirst();
super.add(e);
return true;
} else {
// new element is less than the least in queue => return false
return false;
}
}
}
}
(где NaturalComparator
взято из этого вопроса)
Вы можете использовать SortedSet, например. TreeSet с пользовательским компаратором и удалите наименьшее, когда размер достигнет N.
Как вы можете сказать, что Lucene не поддерживает пользовательский компаратор?
Его абстракция и вы должны реализовать абстрактный метод lessThan(T a, T b)
Хотя старый вопрос, но он может быть полезен кому-то другому. Вы можете использовать minMaxPriorityQueue в библиотеке Google Java guava.
Я не могу придумать готовый к использованию, но вы можете проверить мою реализацию этой коллекции с аналогичными требованиями.
Разница заключается в компараторе, но если вы простираетесь от PriorityQueue
, вы получите его. И при каждом добавлении проверьте, не достигли ли вы предела, и если у вас есть - отбросьте последний элемент.
Ниже приведена реализация, которую я использовал раньше. Соответствует предложению Питера.
public @interface NonThreadSafe {
}
/**
* A priority queue implementation with a fixed size based on a {@link TreeMap}.
* The number of elements in the queue will be at most {@code maxSize}.
* Once the number of elements in the queue reaches {@code maxSize}, trying to add a new element
* will remove the greatest element in the queue if the new element is less than or equal to
* the current greatest element. The queue will not be modified otherwise.
*/
@NonThreadSafe
public static class FixedSizePriorityQueue<E> {
private final TreeSet<E> treeSet; /* backing data structure */
private final Comparator<? super E> comparator;
private final int maxSize;
/**
* Constructs a {@link FixedSizePriorityQueue} with the specified {@code maxSize}
* and {@code comparator}.
*
* @param maxSize - The maximum size the queue can reach, must be a positive integer.
* @param comparator - The comparator to be used to compare the elements in the queue, must be non-null.
*/
public FixedSizePriorityQueue(final int maxSize, final Comparator<? super E> comparator) {
super();
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize = " + maxSize + "; expected a positive integer.");
}
if (comparator == null) {
throw new NullPointerException("Comparator is null.");
}
this.treeSet = new TreeSet<E>(comparator);
this.comparator = treeSet.comparator();
this.maxSize = maxSize;
}
/**
* Adds an element to the queue. If the queue contains {@code maxSize} elements, {@code e} will
* be compared to the greatest element in the queue using {@code comparator}.
* If {@code e} is less than or equal to the greatest element, that element will be removed and
* {@code e} will be added instead. Otherwise, the queue will not be modified
* and {@code e} will not be added.
*
* @param e - Element to be added, must be non-null.
*/
public void add(final E e) {
if (e == null) {
throw new NullPointerException("e is null.");
}
if (maxSize <= treeSet.size()) {
final E firstElm = treeSet.first();
if (comparator.compare(e, firstElm) < 1) {
return;
} else {
treeSet.pollFirst();
}
}
treeSet.add(e);
}
/**
* @return Returns a sorted view of the queue as a {@link Collections#unmodifiableList(java.util.List)}
* unmodifiableList.
*/
public List<E> asList() {
return Collections.unmodifiableList(new ArrayList<E>(treeSet));
}
}
Я был бы признателен за любую обратную связь.
EDIT: Похоже, что использование TreeSet
не очень эффективно, потому что вызовы на first()
кажутся сублинейными. Я изменил TreeSet
на PriorityQueue
. Модифицированный метод add()
выглядит следующим образом:
/**
* Adds an element to the queue. If the queue contains {@code maxSize} elements, {@code e} will
* be compared to the lowest element in the queue using {@code comparator}.
* If {@code e} is greater than or equal to the lowest element, that element will be removed and
* {@code e} will be added instead. Otherwise, the queue will not be modified
* and {@code e} will not be added.
*
* @param e - Element to be added, must be non-null.
*/
public void add(final E e) {
if (e == null) {
throw new NullPointerException("e is null.");
}
if (maxSize <= priorityQueue.size()) {
final E firstElm = priorityQueue.peek();
if (comparator.compare(e, firstElm) < 1) {
return;
} else {
priorityQueue.poll();
}
}
priorityQueue.add(e);
}
Именно то, что я искал. Однако реализация содержит ошибку:
А именно: если элементыLeft > 0 и e уже содержатся в TreeSet. В этом случае elementLeft уменьшается, но количество элементов в TreeSet остается неизменным.
Я бы предложил заменить соответствующие строки в методе add() на
} else if (elementsLeft > 0) {
// queue isn't full => add element and decrement elementsLeft
boolean added = super.add(e);
if (added) {
elementsLeft--;
}
return added;
Попробуйте этот код:
public class BoundedPQueue<E extends Comparable<E>> {
/**
* Lock used for all public operations
*/
private final ReentrantLock lock;
PriorityBlockingQueue<E> queue ;
int size = 0;
public BoundedPQueue(int capacity){
queue = new PriorityBlockingQueue<E>(capacity, new CustomComparator<E>());
size = capacity;
this.lock = new ReentrantLock();
}
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
E vl = null;
if(queue.size()>= size) {
vl= queue.poll();
if(vl.compareTo(e)<0)
e=vl;
}
try {
return queue.offer(e);
} finally {
lock.unlock();
}
}
public E poll() {
return queue.poll();
}
public static class CustomComparator<E extends Comparable<E>> implements Comparator<E> {
@Override
public int compare(E o1, E o2) {
//give me a max heap
return o1.compareTo(o2) *-1;
}
}
}
Вот один, который я собрал, если у вас есть гуава. Я думаю, что это довольно полно. Дайте мне знать, если я что-то пропустил.
Вы можете использовать gouva ForwardingBlockingQueue, поэтому вам не нужно сопоставлять все другие методы.
import com.google.common.util.concurrent.ForwardingBlockingQueue;
public class PriorityBlockingQueueDecorator<E> extends
ForwardingBlockingQueue<E> {
public static final class QueueFullException extends IllegalStateException {
private static final long serialVersionUID = -9218216017510478441L;
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private int maxSize;
private PriorityBlockingQueue<E> delegate;
public PriorityBlockingQueueDecorator(PriorityBlockingQueue<E> delegate) {
this(MAX_ARRAY_SIZE, delegate);
}
public PriorityBlockingQueueDecorator(int maxSize,
PriorityBlockingQueue<E> delegate) {
this.maxSize = maxSize;
this.delegate = delegate;
}
@Override
protected BlockingQueue<E> delegate() {
return delegate;
}
@Override
public boolean add(E element) {
return offer(element);
}
@Override
public boolean addAll(Collection<? extends E> collection) {
boolean modified = false;
for (E e : collection)
if (add(e))
modified = true;
return modified;
}
@Override
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
return offer(e);
}
@Override
public boolean offer(E o) {
if (maxSize > size()) {
throw new QueueFullException();
}
return super.offer(o);
}
}
Создайте PriorityQueue с ограничением по размеру. Он хранит N максимальных номеров.
import java.util.*;
class Demo
{
public static <E extends Comparable<E>> PriorityQueue<E> getPq(final int n, Comparator<E> comparator)
{
return new PriorityQueue<E>(comparator)
{
boolean full()
{
return size() >= n;
}
@Override
public boolean add(E e)
{
if (!full())
{
return super.add(e);
}
else if (peek().compareTo(e) < 0)
{
poll();
return super.add(e);
}
return false;
}
@Override
public boolean offer(E e)
{
if (!full())
{
return super.offer(e);
}
else if (peek().compareTo(e) < 0)
{
poll();
return super.offer(e);
}
return false;
}
};
}
public static void printq(PriorityQueue pq)
{
Object o = null;
while ((o = pq.poll()) != null)
{
System.out.println(o);
}
}
public static void main (String[] args)
{
PriorityQueue<Integer> pq = getPq(2, new Comparator<Integer>(){
@Override
public int compare(Integer i1, Integer i2)
{
return i1.compareTo(i2);
}
});
pq.add(4);
pq.add(1);
pq.add(5);
pq.add(2);
printq(pq);
}
}