0

Java PriorityQueue с фиксированным размером

11

Я рассчитываю большое количество возможных комбинаций алгоритма. Для сортировки этих комбинаций я оцениваю их с помощью значения типа double и сохраняю в PriorityQueue. В настоящее время в этой очереди находится около 200 тыс. элементов, что довольно ресурсоемко по памяти. На самом деле, мне нужны лишь, скажем, лучшие 1000 или даже 100 из всех элементов в списке.

Я начал задумываться, есть ли способ реализовать очередь с приоритетом фиксированного размера в Java. Она должна работать следующим образом: если элемент лучше уже хранящегося, то вставлять его на соответствующую позицию и выбрасывать элемент с наименьшим оценочным значением.

Есть ли у кого-нибудь идеи по этому поводу? Заранее большое спасибо!

Марко

5 ответ(ов)

0

Ваш код выглядит правильно, но, возможно, я неправильно понял ваш вопрос.

Если вы хотите, чтобы ваша очередь хранила только определенное количество элементов, то предложенный вами код будет работать, однако нужно учитывать, что для корректной работы нужно обратить логику в функции compareTo. В противном случае вы будете удалять элемент с наивысшим приоритетом в каждом цикле. Если мы хотим хранить элементы с наибольшими значениями, то функция сравнения должна возвращать положительное значение, когда первое значение лучше второго.

Вот пример функции, которая будет сохранять только наибольшие числа:

public int compare(Double first, Double second) {
    // сохраняем наибольшие значения
    return first > second ? 1 : -1;
}

Таким образом, код будет корректно работать для хранения элементов в очереди с учетом вашего ограничения.

0

В Apache Lucene есть очередь с приоритетами фиксированного размера: http://lucene.apache.org/java/2_4_1/api/org/apache/lucene/util/PriorityQueue.html. На основе моих тестов, она демонстрирует отличную производительность.

0

Чтобы ограничить количество элементов в SortedSet, вы можете использовать TreeSet с пользовательским Comparator. В приведенном вами коде показан процесс добавления элемента, при котором, если размер набора превышает 100, наименьший элемент удаляется, если новый элемент больше. Вот пример кода на русском:

SortedSet<Item> items = new TreeSet<Item>(new Comparator<Item>(...) {
    @Override
    public int compare(Item o1, Item o2) {
        // Ваша логика сравнения
        return ...;
    }
});

void addItem(Item newItem) {
    // Проверяем, превышает ли размер множество 100 элементов
    if (items.size() > 100) {
         // Получаем наименьший элемент
         Item lowest = items.first();
         // Если новый элемент больше наименьшего, удаляем его
         if (newItem.greaterThan(lowest)) {
             items.remove(lowest);
         }
    }

    // Добавляем новый элемент
    items.add(newItem);   
}

Таким образом, данный код эффективно управляет размером SortedSet, сохраняя в нем только 100 элементов, чтобы гарантировать, что добавляемые элементы являются наибольшими. Не забудьте реализовать логику метода compare в вашем компараторе, чтобы сортировка работала корректно.

0

Вы можете использовать метод poll() для очереди, если наименьший элемент в очереди имеет значение, которое хуже текущего элемента. Вот пример реализации на Java:

static <V extends Comparable<? super V>> 
PriorityQueue<V> nbest(int n, Iterable<V> valueGenerator) {
    PriorityQueue<V> values = new PriorityQueue<V>();
    for (V value : valueGenerator) {
        if (values.size() == n && value.compareTo(values.peek()) > 0)
            values.poll(); // удаляем наименьший элемент, так как текущий лучше
        if (values.size() < n) // либо мы удалили элемент, либо очередь еще не заполнена, поэтому добавляем
            values.add(value);
    }
    return values;
}

Этот код предполагает, что у вас есть класс комбинаций, реализующий интерфейс Comparable, который сравнивает комбинации по их рейтингу.

Редактирование: Чтобы уточнить, Iterable в моём примере не обязательно должен быть заранее заполнен. Например, вот Iterable<Integer>, который будет предоставлять все натуральные числа, которые может представить int:

Iterable<Integer> naturals = new Iterable<Integer>() {
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            int current = 0;
            @Override
            public boolean hasNext() {
                return current >= 0;
            }
            @Override
            public Integer next() {
                return current++;
            }
            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
};

Потребление памяти очень умеренное, как вы можете видеть - для более чем 2 миллиардов значений требуется всего два объекта (сам Iterable и Iterator), плюс один int.

Вы, конечно, можете легко адаптировать мой код так, чтобы он не использовал Iterable - я просто использовал его, так как это элегантный способ представления последовательности (к тому же, я слишком много работал с Python и C# ☺).

0

Лучшим подходом было бы более строго контролировать, что попадает в очередь, удаляя и добавляя элементы по мере работы программы. Похоже, есть возможность исключить некоторые элементы до их добавления в очередь. Это будет проще, чем изобретать велосипед, так сказать.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь