web-dev-qa-db-fra.com

Java PriorityQueue à taille fixe

Je calcule un grand nombre de combinaisons résultantes possibles d'un algortihm. Pour trier ces combinaisons, je les note avec une valeur double et les stocke dans PriorityQueue. Actuellement, il y a environ 200 000 éléments dans cette file d'attente, ce qui est à peu près intensif en mémoire. En fait, je n'ai besoin que de dire les 1000 ou 100 meilleurs éléments de la liste. J'ai donc juste commencé à me demander s'il y avait un moyen d'avoir une file d'attente prioritaire avec une taille fixe en Java. Je dois me comporter comme ceci: l'article est-il meilleur que l'un des déjà stockés? Si oui, insérez-le à la position correspondante et jetez l'élément le moins bien noté.

Est-ce que quelqu'un a une idée? Merci encore beaucoup!

Marco

37
Marco
que.add(d);
if (que.size() > YOUR_LIMIT)
     que.poll();

ou ai-je mal compris votre question?

edit: oublié de mentionner que pour que cela fonctionne, vous devez probablement inverser votre fonction comparTo car elle jettera celle qui a la priorité la plus élevée à chaque cycle. (si a est "meilleur" b, comparer (a, b) devrait renvoyer un nombre positif.

exemple pour garder les plus grands nombres utiliser quelque chose comme ceci:

public int compare(Double first, Double second) {
            // keep the biggest values
            return first > second ? 1 : -1;
        }
31
getekha

MinMaxPriorityQueue, Google Guava

Il existe en effet une classe pour maintenir une file d'attente qui, lors de l'ajout d'un élément qui dépasserait la taille maximale de la collection, compare les éléments pour trouver un élément à supprimer et ainsi créer de l'espace: MinMaxPriorityQueue trouvé dans Google Guava à partir de la version 8.

EvictingQueue

Soit dit en passant, si vous souhaitez simplement supprimer l'élément le plus ancien sans comparer les valeurs des objets, Google Guava 15 a obtenu la classe EvictingQueue .

12
Basil Bourque

Il y a une file d'attente prioritaire de taille fixe dans Apache Lucene: http://lucene.Apache.org/Java/2_4_1/api/org/Apache/lucene/util/PriorityQueue.html

Il a d'excellentes performances sur la base de mes tests.

5
Vladimir Giverts

Utilisez SortedSet:

SortedSet<Item> items = new TreeSet<Item>(new Comparator<Item>(...));
...
void addItem(Item newItem) {
    if (items.size() > 100) {
         Item lowest = items.first();
         if (newItem.greaterThan(lowest)) {
             items.remove(lowest);
         }
    }

    items.add(newItem);   
}
2
Victor Sorokin

Juste poll() la file d'attente si son moindre élément est inférieur (dans votre cas, a une note inférieure à) l'élément actuel.

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(); // remove least element, current is better
        if (values.size() < n) // we removed one or haven't filled up, so add
            values.add(value);
    }
    return values;
}

Cela suppose que vous disposez d'une sorte de classe de combinaison qui implémente Comparable qui compare les combinaisons sur leur note.

Edit: Juste pour clarifier, le Iterable dans mon exemple n'a pas besoin d'être prérempli. Par exemple, voici un Iterable<Integer> qui vous donnera tous les nombres naturels qu'un int peut représenter:

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

La consommation de mémoire est très modeste, comme vous pouvez le voir - pour plus de 2 milliards de valeurs, vous avez besoin de deux objets (le Iterable et le Iterator) plus un int.

Vous pouvez bien sûr adapter assez facilement mon code pour qu'il n'utilise pas de Iterable - Je l'ai utilisé parce que c'est une manière élégante de représenter une séquence (aussi, j'en fais trop Python et C # ☺).

2
gustafc

Il semble naturel de simplement conserver les 1000 premiers à chaque fois que vous ajoutez un élément, mais le PriorityQueue n'offre rien pour y parvenir gracieusement. Peut-être que vous pouvez, au lieu d'utiliser un PriorityQueue, faire quelque chose comme ça dans une méthode:

List<Double> list = new ArrayList<Double>();
...
list.add(newOutput);
Collections.sort(list);
list = list.subList(0, 1000);
1
vahidg

Une meilleure approche serait de modérer plus étroitement ce qui se passe dans la file d'attente, en le supprimant et en y ajoutant pendant l'exécution du programme. Il semble qu'il y aurait de la place pour exclure certains éléments avant de les ajouter dans la file d'attente. Ce serait plus simple que de réinventer la roue pour ainsi dire.

0
Gordon