web-dev-qa-db-fra.com

Tri par liste Java: Existe-t-il un moyen de conserver une liste triée de façon permanente, de manière permanente, comme TreeMap?

En Java, vous pouvez créer une ArrayList avec des éléments, puis appeler:

Collections.sort(list, comparator);

Est-il possible de passer le comparateur au moment de la création de la liste, comme vous pouvez le faire avec TreeMap? Le but est de pouvoir ajouter un élément à la liste et au lieu de l’ajouter automatiquement à la fin de la liste, la liste se tiendrait elle-même en fonction du comparateur et insérerait le nouvel élément à l’index déterminé par le comparateur. Il est donc possible que la liste doive être triée à chaque nouvel élément ajouté.

Est-il possible d'y parvenir de cette manière avec le comparateur ou par un autre moyen similaire?

30
Dave L.

Vous pouvez changer le comportement de ArrayList

List<MyType> list = new ArrayList<MyType>() {
    public boolean add(MyType mt) {
         super.add(mt);
         Collections.sort(list, comparator);
         return true;
    }
}; 

Remarque: une PriorityQueue n'est PAS une liste. Si vous ne vouliez pas savoir quel type de collection il s'agissait, le plus simple serait d'utiliser un TreeSet, qui ressemble à un TreeMap mais qui est une collection. Le seul avantage de PriorityQueue est d’autoriser les doublons.

Remarque: le recours aux ressources n'est pas très efficace pour les grandes collections. Il serait plus rapide d'utiliser une recherche binaire et d'insérer une entrée. (mais plus compliqué)

EDIT: Beaucoup dépend de ce que vous avez besoin de la "liste" à faire. Je vous suggère d'écrire un wrapper de liste pour un ArrayList, LinkedList, PriorityQueue, TreeSet ou l'une des autres collections triées et d'implémenter les méthodes qui seront réellement utilisées. De cette façon, vous comprenez bien les exigences de la collection et vous pouvez vous assurer que cela fonctionne correctement pour vous.

EDIT (2): Puisqu'il y avait tellement d'intérêt d'utiliser binarySearch à la place. ;)

List<MyType> list = new ArrayList<MyType>() {
    public boolean add(MyType mt) {
        int index = Collections.binarySearch(this, mt);
        if (index < 0) index = ~index;
        super.add(index, mt);
        return true;
    }
};
45
Peter Lawrey

Tout le monde suggère PriorityQueue. Cependant, il est important de réaliser que si vous parcourez le contenu de PriorityQueue, les éléments ne seront pas triés. Vous êtes uniquement assuré d'obtenir l'élément "minimum" des méthodes peek(), poll(), etc.

Un TreeSet semble être un meilleur ajustement. Les avertissements seraient que, en tant que Set, il ne peut pas contenir d'éléments en double et ne prend pas en charge l'accès aléatoire avec un index.

14
erickson

Commentaire

Il y a probablement une bonne raison pour laquelle il n'y a pas d'implémentation SortedList dans le JDK. Personnellement, je ne vois aucune raison d'avoir un tri automatique dans le JDK.

Cela sent mauvais l'optimisation prématurée qui a mal tourné. Si la liste n'est pas lue aussi souvent qu'elle est insérée, vous gaspillez des cycles de tri répétitif sans raison. Un tri juste avant une lecture serait beaucoup plus réactif et avoir un boolean quelque part indiquant que la liste a ou non besoin d'être triée avant d'être lu serait encore mieux.

Le fait est que vous ne vous souciez que de l'ordre lorsque vous parcourez la liste avec une boucle Iterator ou for each, appelez donc Collections.sort() avant tout code itérant serait probablement plus performant que d'essayer de garder la liste triée tout le temps à chaque insertion.

Il y a des ambiguïtés avec List en raison des doublons, comment commander des doublons de manière déterministe? Il y a SortedSet et cela a du sens en raison de l'unicité. Mais trier une List peut être plus compliqué par les effets secondaires des doublons et d’autres contraintes, telles que rendre chaque objet Comparable ou comme je le montre dans mon code, comme ayant une Comparator capable de faire le travail à la place.

Tri sur .add()

Si vous rencontrez une situation très spéciale dans laquelle un tri automatique List serait utile, vous pouvez sous-classer une implémentation List et ignorer .add() pour créer un Collections.sort(this, comparator) que vous transmettez à un constructeur personnalisé. J'ai utilisé LinkedList au lieu de ArrayList pour une raison, ArrayList est une insertion naturelle triée par ordre List pour commencer. Il a également la capacité de .add() à un index qui est plutôt inutile si vous voulez une constante List, qui devra être gérée de façon quelque peu moins idéale. Selon le Javadoc;

void    add(int index, Object element)

Insère l'élément spécifié dans le fichier position spécifiée dans cette liste (opération facultative).

Donc, lancer UnSupportedOperationException serait acceptable, ou vous pourriez simplement ignorer index et déléguer à .add(Object element); si vous le documentez dans un JavaDoc sur la méthode.

Habituellement, lorsque vous souhaitez effectuer un grand nombre d'insertions/extractions et un tri, vous devez utiliser un paramètre LinkedList en raison de meilleures performances, compte tenu de l'utilisation de `List '.

Voici un exemple rapide:

import Java.util.Collections;
import Java.util.Comparator;
import Java.util.LinkedList;

public class SortedList<E> extends LinkedList<E>
{
    private Comparator<E> comparator;

    public SortedList(final Comparator<E> comparator)
    {
        this.comparator = comparator;
    }

    /**
    * this ignores the index and delegates to .add() 
    * so it will be sorted into the correct place immediately.
    */
    @Override
    public void add(int index, Object element)
    {
        this.add(element);     
    }

    @Override
    public boolean add(final E e)
    {
        final boolean result = super.add(e);
        Collections.sort(this, this.comparator);
        return result;
    }
}

Solution la plus efficace:

Sinon, vous pouvez uniquement trier lorsque vous obtenez la variable Iterator. Ce serait davantage axé sur les performances si l'ordre trié n'était vraiment important que lors de l'itération sur la variable List. Cela couvrirait le cas d'utilisation du code client n'ayant pas à appeler, Collections.sort() avant chaque itération et encapsulant ce comportement dans la classe.

import Java.util.Collections;
import Java.util.Comparator;
import Java.util.Iterator;
import Java.util.LinkedList;

public class SortedList<E> extends LinkedList<E>
{
    private Comparator<E> comparator;

    public SortedList(final Comparator<E> comparator)
    {
        this.comparator = comparator;
    }

    @Override
    public Iterator<E> iterator()
    {
        Collections.sort(this, this.comparator);
        return super.iterator();
    }
}

Bien sûr, il faudrait effectuer une vérification et une gestion des erreurs pour déterminer si la variable Comparator était null ou non et ce qu'il fallait faire si c'était le cas, mais cela vous en donne l'idée. Vous n'avez toujours pas de méthode déterministe pour traiter les doublons.

Solution de goyave:

Si vous utilisez Guava et vous devriez le faire, vous pouvez simplement utiliser

Ordering.immutableSortedCopy() uniquement lorsque vous devez effectuer une itération et en avoir terminé.

5
user177800

Quelque chose comme TreeSet (ou TreeMultiset si vous avez besoin de doublons) avec un accès aléatoire plus efficace est possible, mais je doute que cela ait été implémenté en Java. Faire en sorte que chaque nœud de l’arbre se souvienne de la taille de son sous-arbre de gauche permet d’accéder à un élément par index dans le temps O(log(size)) qui n’est pas mauvais.

Pour l'implémenter, vous devez réécrire une bonne partie de la TreeMap sous-jacente.

4
maaartinus

Je voudrais utiliser un GuavaTreeMultiset en supposant que vous voulez un List parce que vous pouvez avoir des éléments en double. Ça fera tout ce que tu veux. La seule chose dont il n’aura pas accès, c’est l’accès indexé, ce qui n’a pas beaucoup de sens étant donné que vous ne mettez pas des éléments dans les index de votre choix. L’autre élément à prendre en compte est qu’il ne stockera pas les doublons d’objets equal, mais simplement leur nombre.

2
ColinD

commons-collections ont TreeBag

Au départ, j'ai suggéré PriorityQueue, mais son ordre d'itération n'est pas défini, il est donc inutile, sauf si vous le répétez en récupérant la tête d'un clone de la file jusqu'à ce qu'elle soit vide.

Puisque vous êtes probablement concerné par l'ordre des itérations, je pense que vous pouvez remplacer la méthode iterator():

public class OrderedIterationList<E> extends ArrayList<E> {
    @Override
    public Iterator<E> iterator() {
        Object[] array = this.toArray(); // O(1)
        Arrays.sort(array);
        return Arrays.asList(array).iterator(); // asList - O(1)
    }
}

Vous pouvez améliorer cela en stockant un instantané de la collection triée et utilisez modCount pour vérifier si la collection n'est pas modifiée.

Selon les cas d'utilisation, cela peut être moins ou plus efficace que la suggestion de Peter. Par exemple, si vous ajoutez plusieurs éléments et effectuez une itération. (sans ajouter d'éléments entre les itérations), cela pourrait alors être plus efficace.

2
Bozho

Considérez indexed-tree-map que j'ai créé en faisant face à un problème similaire, vous pourrez accéder aux éléments par index et obtenir un index des éléments tout en conservant l'ordre de tri. Les doublons peuvent être placés dans des tableaux en tant que valeurs sous la même clé. 

1
Vitaly Sazanovich

Le seul moyen d'avoir une structure triée avec moins de O(n) temps pour ajouter/indexOf/remove/get est d'utiliser un arbre. Dans ce cas, les opérations ont généralement O(log2n) et traverse est comme O (1).

O (n) est juste une liste chaînée.


Edition: insertion dans la liste chaînée avec recherche binaire. Pour les opérations d'insertion, n'utilisant pas de structure binaire, ni de petites tailles, cela devrait être optimal.

@Peter: Il y a l'algo w/O(log2n) compare (qui sont lents) pour insérer et O(n) se déplace . Si vous devez remplacer LinkedList, alors que ce soit. Mais c'est aussi beau que possible. Je garde l'algorithme aussi propre que possible pour qu'il soit facilement compréhensible, il peut être optimisé un peu.

package t1;

import Java.util.LinkedList;
import Java.util.List;
import Java.util.ListIterator;
import Java.util.Random;

public class SortedList {


    private static <T> int binarySearch(ListIterator<? extends Comparable<? super T>> i, T key){
        int low = 0;
        int high= i.previousIndex();
        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = get(i, mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid;
        }
        return -(low + 1);  // key not found
    }

    private static <T> T get(ListIterator<? extends T> i, int index) {
        T obj = null;
        int pos = i.nextIndex();
        if (pos <= index) {
            do {
                obj = i.next();
            } while (pos++ < index);
        } else {
            do {
                obj = i.previous();
            } while (--pos > index);
        }
        return obj;
    }
    private static void move(ListIterator<?> i, int index) {        
        int pos = i.nextIndex();
        if (pos==index)
            return;

        if (pos < index) {
            do {
                i.next();
            } while (++pos < index);
        } 
        else {
            do {
                i.previous();
            } while (--pos > index);
        }
    }
    @SuppressWarnings("unchecked")
    static  <T> int insert(List<? extends Comparable<? super T>> list, T key){
        ListIterator<? extends Comparable<? super T>> i= list.listIterator(list.size());
        int idx = binarySearch(i, key); 
        if (idx<0){
            idx=~idx;
        }
        move(i, idx);
        ((ListIterator<T>)i).add(key);
        return i.nextIndex()-1;
    }

    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        LinkedList<Integer> unsorted = new LinkedList<Integer>();
        Random r =new Random(11);
        for (int i=0;i<33;i++){
            Integer n = r.nextInt(17);
            insert(list, n);
            unsorted.add(n);            
        }

        System.out.println("  sorted: "+list);
        System.out.println("unsorted: "+unsorted);
    }
1
bestsss

Dans la hiérarchie JavaFX TransformationList, il existe quelque chose appelé SortedList. La liste est entièrement observable afin que les ajouts/suppressions avertissent les autres auditeurs qui la regardent.

L’approche de base consiste à surveiller les autres modifications dans ObservableList et à utiliser de manière stratégique Collections.binarySearch () comme d’autres l’ont suggéré pour localiser l’index de l’ajout ou de la suppression dans le temps Olog (n).

Il y a un problème que je n'ai pas vu mentionné ici et qui concerne la possibilité de suivre les éléments ajoutés ayant la même signature compareTo, c'est-à-dire T1.compareTo (T2) == 0. Dans ce cas, la liste triée (I posterai mon propre code source ci-dessous) doit avoir un type d’élément wrapper, que j’appellerai Element. Ceci est similaire à ce que les créateurs de JavaFX ont fait avec SortedList . La raison en est entièrement due aux opérations de suppression, il est impossible de localiser l'élément d'origine s'il existe des doublons compareTo. Normalement, dans une implémentation NavigableSet telle que TreeSet, ces doublons n'entrent jamais dans l'ensemble. Une liste est différente.

J'ai une bibliothèque de listes observables qui peuvent être efficacement chaînées (très similaire à Java Streams) et qui propagent entièrement les résultats en aval en tant que source précédente dans les mises à jour de la chaîne.

Hiérarchie des classes

Interface

/**
 * Binds the elements of this list to the function application of each element of a
 * source observable list.
 * <p>
 * While a {@code IListContentBinding} is bound, any attempt to modify its contents
 * will result in an {@code UnsupportedOperationException}. To unbind the list, call
 * {@link #unbind() unbind}.
 *
 * @param <S> The element type of the input source list that will generate change
 *            events.
 * @param <T> The element type of this output list.
 */
public interface IListContentBinding<S, T> extends ObservableList<T>, ObservableListValue<T>, IContentBinding {... details not shown ....}

Classe de base abstraite pour tous les types de reliure (Tri, Distinct, Map, FlatMap, etc.)

/**
 * Binds the elements of this list to the function application of each element of a
 * source observable list.
 * <p>
 * While a {@code ListContentBinding} is bound, any attempt to modify its contents
 * will result in an {@code UnsupportedOperationException}. To unbind the list, call
 * {@link #unbind() unbind}.
 *
 * @param <S> The element type of the source list that will generate change events.
 * @param <T> The element type of this binding.
 */
public abstract class ListContentBinding<S, T> extends ObservableListWrapper<T>
    implements IListContentBinding<S, T> {.... details not shown ....}

Classer la classe de reliure

/**
 * A {@code ListContentBinding} implementation that generates sorted elements from a
 * source list. The comparator can be set to another {@code Comparator} function at
 * any time through the {@link #comparatorProperty() comparator} property.
 * <p>
 * Unlike the Collections {@link Collections#sort(List) list sort} or Arrays
 * {@link Arrays#sort(Object[]) array sort}, once this binding has been added to the
 * order of duplicate elements cannot be guaranteed to match the original order of
 * the source list. That is the insertion and removal mechanism do not guarantee that
 * the original order of duplicates (those items where T1.compareTo(T2) == 0) is
 * preserved. However, any removal from the source list is <i>guaranteed</i> to
 * remove the exact object from this sorted list. This is because an int <i>ID</i> field
 * is added to the wrapped item through the {@link Element} class to ensure that
 * matching duplicates can be further compared.
 * <p>
 * Added/Removed objects from the source list are placed inside this sorted list
 * through the {@link Arrays#binarySearch(Object[], Object, Comparator) array binary
 * search} algorithm. For any duplicate item in the sorted list, a further check on
 * the ID of the {@code Element} corresponding to that item is compared to the
 * original, and that item. Each item added to this sorted list increases the
 * counter, the maximum number of items that should be placed in this list should be
 * no greater than {@code Integer.MAX_VALUE - Integer.MIN_VALUE}, or 4,294,967,295
 * total elements. Sizes greater than this value for an instance of this class
 * may produce unknown behavior.
 * <p>
 * Removal and additions to this list binding are proportional to <i>O(logn)</i>
 * runtime, where <i>n</i> is the current total number of elements in this sorted
 * list.
 *
 * @param <T> The element type of the source and this list binding.
 */
class ListContentSortBinding<T> extends ListContentBinding<T, T> implements IListContentSortBinding<T> {

    /**
     * Each location in the source list has a random value associated it with to deal
     * with duplicate elements that would return T1.compareTo(T2) == 0.
     */
    private Element[] elements = newElementArray(10);

    /**
     * The same elements from {@link #elements} but placed in their correct sorted
     * position according to the {@link #elementComparator element comparator}.
     */
    protected Element[] sortedElements = newElementArray(10);

    /**
     * Create a new instance.
     *
     * @param source The source observable list. Sorted elements will be generated
     *            from the source and set as the content of this list binding.
     * @param comparator The sorter. An observable that will update the comparator of
     *            this binding when invalidated. The sorter can be set to another
     *            {@code Comparator} function at anytime through the
     *            {@link #comparatorProperty() comparator} property.
     * @param options The options of this binding. Considers {@code DependencyOption}
     *            instances.
     *            <p>
     *            All bindings consider {@code BeforeChangeOption} and
     *            {@code AfterChangeOption}.
     */
    @SafeVarargs
    ListContentSortBinding(ObservableList<T> source, ObservableObjectValue<Comparator<? super T>> comparator,
        BindingOption<T, T>... options) {
        this(source, comparator.get(), options);

        comparatorProperty().bind(comparator);
    }

    /**
     * Create a new instance.
     *
     * @param source The source observable list. Sorted elements will be generated
     *            from the source and set as the content of this list binding.
     * @param comparator The sorter. The sorter can be set to another
     *            {@code Comparator} function at anytime through the
     *            {@link #comparatorProperty() comparator} property.
     * @param options The options of this binding. Considers {@code DependencyOption}
     *            instances.
     *            <p>
     *            All bindings consider {@code BeforeChangeOption} and
     *            {@code AfterChangeOption}.
     */
    @SafeVarargs
    ListContentSortBinding(ObservableList<T> source, Comparator<? super T> comparator,
        BindingOption<T, T>... options) {
        super(new ArrayList<>(), options);

        List<Observable> observables = new ArrayList<>(
            Arrays.asList(BindingOptionBuilder.extractDependencies(options)));

        setComparator(comparator);
        observables.add(comparatorProperty());

        bind(source, observables.toArray(new Observable[observables.size()]));
    }

    @Override
    protected void sourceChanged(Change<? extends T> change) {
        List<? extends T> source = change.getList();

        while (change.next()) {
            int from = change.getFrom();

            if (change.wasPermutated() || change.wasUpdated()) {
                List<? extends T> srcMod = source.subList(from, change.getTo());

                removed(source, from, srcMod.size());
                added(source, from, srcMod);
            } else {
                List<? extends T> removed = change.getRemoved();
                List<? extends T> added = change.getAddedSubList();

                if (change.wasReplaced()) {
                    int min = Math.min(added.size(), removed.size());
                    replaced(source, from, added.subList(0, min));

                    added = added.subList(min, added.size());
                    removed = removed.subList(min, removed.size());
                }

                if (removed.size() > 0) {
                    removed(source, from, removed.size());
                }

                if (added.size() > 0) {
                    if (source.size() >= elements.length) {
                        ensureSize(source.size());
                    }

                    added(source, from, added);
                }

                ensureSize(source.size());
            }
        }
    }

    /**
     * Replace the items in this sorted list binding resulting from a replacement
     * operation in the source list. For each of the items added starting at the
     * <i>from</i> index in the source list, and items was removed at the same source
     * position.
     *
     * @param source The source list.
     * @param from The index of where the replacement started in the source
     *            (inclusive). The removed and added elements occurred starting at
     *            the same source position.
     * @param added The added source elements from the change.
     */
    @SuppressWarnings({})
    private void replaced(List<? extends T> source, int from, List<? extends T> added) {
        int oldSize = size();

        for (int i = 0; i < added.size(); i++) {
            int index = from + i;
            Element e = elements[index];

            // Find the old element and remove it
            int pos = findPosition(e, index, oldSize);

            System.arraycopy(sortedElements, pos + 1, sortedElements, pos, oldSize - pos - 1);

            remove(pos);

            T t = added.get(i);

            // Create a new element and add it
            e = new Element(t);

            elements[index] = e;

            pos = findPosition(e, index, oldSize - 1);

            if (pos < 0) {
                pos = ~pos;
            }

            System.arraycopy(sortedElements, pos, sortedElements, pos + 1, oldSize - pos - 1);
            sortedElements[pos] = e;

            add(pos, t);
        }
    }

    /**
     * Add the elements from the source observable list to this binding.
     *
     * @param source The source list.
     * @param from The index of where the addition started in the source (inclusive).
     * @param added The added source elements from the change.
     */
    @SuppressWarnings({})
    private void added(List<? extends T> source, int from, List<? extends T> added) {
        if (size() == 0) {
            int size = added.size();
            Element[] temp = newElementArray(size);

            for (int i = 0; i < added.size(); i++) {
                T t = added.get(i);
                Element e = new Element(t);

                elements[i] = e;
                temp[i] = e;
            }

            if (elementComparator == null) {
                addAll(added);
                return;
            }

            Arrays.sort(temp, elementComparator);
            System.arraycopy(temp, 0, sortedElements, 0, temp.length);

            addAll(Arrays.stream(temp).map(e -> (T) e.t).collect(Collectors.toList()));

            return;
        }

        int size = size();
        System.arraycopy(elements, from, elements, from + added.size(), size - from);

        for (int i = 0; i < added.size(); i++) {
            int index = from + i;

            T t = added.get(i);
            Element e = new Element(t);

            int pos = findPosition(e, index, size);

            if (pos < 0) {
                pos = ~pos;
            }

            elements[index] = e;

            if (pos < size) {
                System.arraycopy(sortedElements, pos, sortedElements, pos + 1, size - pos);
            }

            sortedElements[pos] = e;

            add(pos, t);
            size++;
        }
    }

    /**
     * Remove the elements from this binding that were removed from the source list.
     * Update the {@link #elements} mapping.
     *
     * @param source The source list.
     * @param from The index of where the removal started in the source (inclusive).
     * @param removedSize The total number of removed elements from the source list
     *            for the change.
     */
    @SuppressWarnings({})
    private void removed(List<? extends T> source, int from, int removedSize) {
        if (source.size() == 0) {
            elements = newElementArray(10);
            sortedElements = newElementArray(10);
            elementCounter = Integer.MIN_VALUE;
            clear();
            return;
        }

        int oldSize = size();
        int size = oldSize;

        for (int i = 0; i < removedSize; i++) {
            int index = from + i;

            Element e = elements[index];

            int pos = findPosition(e, index, size);

            System.arraycopy(sortedElements, pos + 1, sortedElements, pos, size - pos - 1);

            remove(pos);
            sortedElements[--size] = null;
        }

        System.arraycopy(elements, from + removedSize, elements, from, oldSize - from - removedSize);

        for (int i = size; i < oldSize; i++) {
            elements[i] = null;
        }
    }

    /**
     * Locate the position of the element in this sorted binding by performing a
     * binary search. A binary search locates the index of the add in Olog(n) time.
     *
     * @param e The element to insert.
     * @param sourceIndex The index of the source list of the modification.
     * @param size The size of the array to search, exclusive.
     *
     * @return The position in this binding that the element should be inserted.
     */
    private int findPosition(Element e, int sourceIndex, int size) {
        if (size() == 0) {
            return 0;
        }

        int pos;

        if (elementComparator != null) {
            pos = Arrays.binarySearch(sortedElements, 0, size, e, elementComparator);
        } else {
            pos = sourceIndex;
        }

        return pos;
    }

    /**
     * Ensure that the element array is large enough to handle new elements from the
     * source list. Also shrinks the size of the array if it has become too large
     * with respect to the source list.
     *
     * @param size The minimum size of the array.
     */
    private void ensureSize(int size) {
        if (size >= elements.length) {
            int newSize = size * 3 / 2 + 1;

            Element[] replacement = newElementArray(newSize);
            System.arraycopy(elements, 0, replacement, 0, elements.length);
            elements = replacement;

            replacement = newElementArray(newSize);
            System.arraycopy(sortedElements, 0, replacement, 0, sortedElements.length);
            sortedElements = replacement;

        } else if (size < elements.length / 4) {
            int newSize = size * 3 / 2 + 1;

            Element[] replacement = newElementArray(newSize);
            System.arraycopy(elements, 0, replacement, 0, replacement.length);
            elements = replacement;

            replacement = newElementArray(newSize);
            System.arraycopy(sortedElements, 0, replacement, 0, replacement.length);
            sortedElements = replacement;
        }
    }

    /**
     * Combines the {@link #comparatorProperty() item comparator} with a secondary
     * comparison if the items are equal through the <i>compareTo</i> operation. This
     * is used to quickly find the original item when 2 or more items have the same
     * comparison.
     */
    private Comparator<Element> elementComparator;

    /**
     * @see #comparatorProperty()
     */
    private ObjectProperty<Comparator<? super T>> comparator =
        new SimpleObjectProperty<Comparator<? super T>>(this, "comparator") {
            @Override
            protected void invalidated() {
                Comparator<? super T> comp = get();

                if (comp != null) {
                    elementComparator = Comparator.nullsLast((e1, e2) -> {
                        int c = comp.compare(e1.t, e2.t);
                        return c == 0 ? Integer.compare(e1.id, e2.id) : c;
                    });
                } else {
                    elementComparator = null;
                }
            }
        };

    @Override
    public final ObjectProperty<Comparator<? super T>> comparatorProperty() {
        return comparator;
    }

    @Override
    public final Comparator<? super T> getComparator() {
        return comparatorProperty().get();
    }

    @Override
    public final void setComparator(Comparator<? super T> comparator) {
        comparatorProperty().set(comparator);
    }

    @Override
    protected void onInvalidating(ObservableList<T> source) {
        clear();
        ensureSize(source.size());
        added(source, 0, source);
    }

    /**
     * Counter starts at the Integer min value, and increments each time a new
     * element is requested. If this list becomes empty, the counter is restarted at
     * the min value.
     */
    private int elementCounter = Integer.MIN_VALUE;

    /**
     * Generate a new array of {@code Element}.
     *
     * @param size The size of the array.
     *
     * @return A new array of null Elements.
     */
    @SuppressWarnings("unchecked")
    private Element[] newElementArray(int size) {
        return new ListContentSortBinding.Element[size];
    }

Classe d'élément d'emballage

    /**
     * Wrapper class to further aid in comparison of two object types &lt;T>. Since
     * sorting in a list allows duplicates we must assure that when a removal occurs
     * from the source list feeding this binding that the removed element matches. To
     * do this we add an arbitrary <i>int</i> field inside this element class that
     * wraps around the original object type &lt;T>.
     */
    final class Element {
        /** Object */
        private final T t;
        /** ID helper for T type duplicates */
        private int id;

        Element(T t) {
            this.t = Objects.requireNonNull(t);
            this.id = elementCounter++;
        }

        @Override
        public String toString() {
            return t.toString() + " (" + id + ")";
        }
    }
}

TEST DE VÉRIFICATION DE JUNIT

@Test
public void testSortBinding() {
    ObservableList<IntWrapper> source = FXCollections.observableArrayList();

    int size = 100000;

    for (int i = 0; i < size / 2; i++) {
        int index = (int) (Math.random() * size / 10);
        source.add(new IntWrapper(index));
    }

    ListContentSortBinding<IntWrapper> binding =
        (ListContentSortBinding<IntWrapper>) CollectionBindings.createListBinding(source).sortElements();

    Assert.assertEquals("Sizes not equal for sorted binding | Expected: " +
        source.size() + ", Actual: " + binding.size(),
        source.size(), binding.size());

    List<IntWrapper> sourceSorted = new ArrayList<>(source);
    Collections.sort(sourceSorted);

    for (int i = 0; i < source.size(); i++) {
        IntWrapper expected = sourceSorted.get(i);
        IntWrapper actual = binding.get(i);

        Assert.assertEquals("Elements not equal in expected sorted lists | Expected: " +
            expected.value + ", Actual: " + actual.value,
            expected.value, actual.value);
    }

    System.out.println("Sorted Elements Equal: Complete.");

    // Randomly add chunks of elements at random locations in the source

    int addSize = size / 10000;

    for (int i = 0; i < size / 4; i++) {
        List<IntWrapper> added = new ArrayList<>();
        int toAdd = (int) (Math.random() * addSize);

        for (int j = 0; j < toAdd; j++) {
            int index = (int) (Math.random() * size / 10);
            added.add(new IntWrapper(index));
        }

        int atIndex = (int) (Math.random() * source.size());
        source.addAll(atIndex, added);
    }

    sourceSorted = new ArrayList<>(source);
    Collections.sort(sourceSorted);

    for (int i = 0; i < source.size(); i++) {
        IntWrapper expected = sourceSorted.get(i);
        IntWrapper actual = binding.get(i);

        Assert.assertEquals("Elements not equal in expected sorted lists | Expected: " +
            expected.value + ", Actual: " + actual.value,
            expected.value, actual.value);
    }

    System.out.println("Sorted Elements Equal - Add Multiple Elements: Complete.");

    // Remove one element at a time from the source list and compare
    // to the elements that were removed from the sorted binding
    // as a result. They should all be identical index for index.

    List<IntWrapper> sourceRemoved = new ArrayList<>();
    List<IntWrapper> bindingRemoved = new ArrayList<>();

    ListChangeListener<IntWrapper> bindingListener = change -> {
        while (change.next()) {
            if (change.wasRemoved()) {
                bindingRemoved.addAll(change.getRemoved());
            }
        }
    };

    // Watch the binding for changes after the upstream source changes

    binding.addListener(bindingListener);

    for (int i = 0; i < size / 4; i++) {
        int index = (int) (Math.random() * source.size());
        IntWrapper removed = source.remove(index);
        sourceRemoved.add(removed);
    }

    for (int i = 0; i < bindingRemoved.size(); i++) {
        IntWrapper expected = bindingRemoved.get(i);
        IntWrapper actual = sourceRemoved.get(i);

        Assert.assertEquals("Elements not equal in expected sorted lists | Expected: " +
            expected + ", Actual: " + actual,
            expected.value, actual.value);

        Assert.assertEquals("Element refs not equal in expected sorted lists | Expected: " +
            expected.value + ", Actual: " + actual.value,
            expected.r, actual.r, 0);
    }

    System.out.println("Sorted Remove Single Element: Complete.");

    // Replace random elements from the source list

    bindingRemoved.clear();
    sourceRemoved.clear();
    int removeSize = size / 10000;

    for (int i = 0; i < size / 1000; i++) {
        int replaceIndex = (int) (Math.random() * source.size());

        int index = (int) (Math.random() * size / 10);
        IntWrapper replace = new IntWrapper(index);

        source.set(replaceIndex, replace);
    }

    sourceSorted = new ArrayList<>(source);
    Collections.sort(sourceSorted);

    for (int i = 0; i < source.size(); i++) {
        IntWrapper expected = sourceSorted.get(i);
        IntWrapper actual = binding.get(i);

        Assert.assertEquals("Elements not equal in expected sorted lists | Expected: " +
            expected.value + ", Actual: " + actual.value,
            expected.value, actual.value);
    }

    System.out.println("Sorted Elements Replace: Complete.");

    // Remove random chunks from the source list

    bindingRemoved.clear();
    sourceRemoved.clear();
    Set<IntWrapper> sourceRemovedSet =
        Collections.newSetFromMap(new IdentityHashMap<>()); // set for speed

    while (source.size() > 0) {
        int index = (int) (Math.random() * source.size());
        int toRemove = (int) (Math.random() * removeSize);
        toRemove = Math.min(toRemove, source.size() - index);

        List<IntWrapper> removed = source.subList(index, index + toRemove);
        sourceRemovedSet.addAll(new ArrayList<>(removed));

        removed.clear(); // triggers list change update to binding
    }

    Assert.assertEquals(bindingRemoved.size(), sourceRemovedSet.size());

    // The binding removed will not necessarily be placed in the same order
    // since the change listener on the binding will make sure that the final
    // order of the change from the binding is in the same order as the binding
    // element sequence. We therefore must do a contains() to test.

    for (int i = 0; i < bindingRemoved.size(); i++) {
        IntWrapper expected = bindingRemoved.get(i);

        Assert.assertTrue("Binding Removed Did Not Contain Source Removed",
            sourceRemovedSet.contains(expected));
    }

    System.out.println("Sorted Removed Multiple Elements: Complete.");
}

TEST BENCHMARK JUNIT

  @Test
public void sortBindingBenchmark() {
    ObservableList<IntWrapper> source = FXCollections.observableArrayList();

    ObservableList<IntWrapper> binding =
        (ListContentSortBinding<IntWrapper>) CollectionBindings.createListBinding(source).sortElements();

    int size = 200000;

    Set<IntWrapper> toAdd = new TreeSet<>();

    while (toAdd.size() < size) {
        int index = (int) (Math.random() * size * 20);
        toAdd.add(new IntWrapper(index));
    }

    // Randomize the order
    toAdd = new HashSet<>(toAdd);

    System.out.println("Sorted Binding Benchmark Setup: Complete.");

    long time = System.currentTimeMillis();

    for (IntWrapper w : toAdd) {
        source.add(w);
    }

    long bindingTime = System.currentTimeMillis() - time;

    System.out.println("Sorted Binding Time: Complete.");

    source.clear(); // clear the list and re-add

    ObservableList<IntWrapper> sortedList = new SortedList<>(source);

    time = System.currentTimeMillis();

    for (IntWrapper w : toAdd) {
        source.add(w);
    }

    long sortedListTime = System.currentTimeMillis() - time;

    System.out.println("JavaFX Sorted List Time: Complete.");

    // Make the test "fair" by adding a listener to an observable
    // set that populates the sorted set

    ObservableSet<IntWrapper> obsSet = FXCollections.observableSet(new HashSet<>());
    Set<IntWrapper> sortedSet = new TreeSet<>();

    obsSet.addListener((SetChangeListener<IntWrapper>) change -> {
        sortedSet.add(change.getElementAdded());
    });

    time = System.currentTimeMillis();

    for (IntWrapper w : toAdd) {
        obsSet.add(w);
    }

    long setTime = System.currentTimeMillis() - time;

    System.out.println("Sorted Binding Benchmark Time: Complete");

    Assert.assertEquals(sortedSet.size(), binding.size());

    System.out.println("Binding: " + bindingTime + " ms, " +
        "JavaFX Sorted List: " + sortedListTime + " ms, " +
        "TreeSet: " + setTime + " ms");
}

Classe d'emballage pour les tests uniquement

    /**
     * Wrapper class for testing sort bindings. Verifies that duplicates were sorted
     * and removed correctly based on the object instance.
     */
private static class IntWrapper implements Comparable<IntWrapper> {
    static int counter = Integer.MIN_VALUE;
    final int value;
    final int id;

    IntWrapper(int value) {
        this.value = value;
        this.id = counter++;
    }
1
ghostNet

La solution évidente consiste à créer votre propre classe qui implémente l'interface Java.util.List et prend un Comparator comme argument du constructeur. Vous utiliseriez le comparateur aux bons endroits, c’est-à-dire que la méthode add parcourait les éléments existants et insérait le nouvel élément au bon endroit. Vous interdiriez les appels à des méthodes telles que add(int index, Object obj) et ainsi de suite.

En fait, il faut que quelqu'un ait déjà créé cela ... une recherche rapide sur Google en révèle au moins un exemple:

http://www.ltg.ed.ac.uk/NITE/nxt/apidoc/net/sourceforge/nite/util/SortedList.html

1
Eric Giguere

La principale différence entre SortedSet et List est:

  • SortedSet conserve son élément dans le bon ordre, mais vous ne pouvez pas accéder à un élément spécifique par index.
  • La liste permet un accès indexé et un ordre arbitraire des éléments. Cela permet également de changer n'importe quel élément (par index ou Iterator) en un autre, sans que l'emplacement change.

Vous semblez vouloir une fusion des deux: le tri automatique et l’autorisation d’un accès (rapide) aux index. Selon la taille des données et la fréquence de lecture indexée ou d’ajout de nouveaux éléments, voici mes idées:

  • un ArrayList encapsulé, où la méthode add a utilisé un ListIterator pour rechercher le point d'insertion, puis y insérant l'élément. C'est O(n) pour les insertions, O(1) pour les accès indexés.
  • une LinkedList encapsulée, où la méthode add a utilisé un ListIterator pour rechercher le point d'insertion, puis y insérant l'élément. (Ceci est toujours O(n) pour les insertions (avec un facteur parfois bien plus petit que ArrayList, parfois même plus), ainsi que pour l'accès indexé.)
  • une arborescence binaire modifiée gardant la trace de la taille des deux moitiés à chaque niveau, permettant ainsi un accès indexé. (Ceci serait O (log n) pour chaque accès, mais nécessite une programmation supplémentaire, car il n'est pas encore inclus dans Java SE. Ou vous trouverez une bibliothèque qui peut le faire.)

Dans tous les cas, les interfaces et les contrats de SortedSet et List ne sont pas vraiment compatibles. Vous souhaitez donc que la partie List soit en lecture seule (ou en lecture-suppression uniquement), ne permettant pas la configuration et l'ajout, ni object (peut-être en implémentant l'interface Collection) pour l'ajout d'objets.

1
Paŭlo Ebermann

SortedSet

Toute implémentation de l'interface SortedSet porte le comportement souhaité. 

Par défaut, les objets ajoutés sont triés dans leur ordre naturel, c'est-à-dire en fonction de leur implémentation de la méthode Comparable::compareTo interface.

Vous pouvez également passer une implémentation Comparator pour déterminer le tri. 

TreeSet

La TreeSet est une implantation couramment utilisée de SortedSet. Vous pouvez aussi en trouver d'autres. 

Les doublons

La principale différence entre une List et une SortedSet réside dans les doublons, objets comparés de la même manière. Un List autorise les doublons, alors qu'un SortedSet, comme tout Set, ne le permet pas. 

Accès par index

Une autre différence est qu’on ne peut pas accéder à Set par index. Vous ne pouvez pas localiser un objet par son numéro de position dans la collection. 

Si vous avez besoin de cet accès après avoir construit votre SortedSet, créez un List. Il existe plusieurs façons de procéder, telles que passer la SortedSet au constructeur de ArrayList. Une façon récente de faire, à partir de Java 10, consiste à créer une List non modifiable en passant la SortedSet à List.copyOf

0
Basil Bourque

La meilleure façon de le faire serait de remplacer l'implémentation d'une liste par l'ajout ..__ Je vais utiliser une liste LinkedList pour le démontrer, car cela permet une insertion efficace.

public boolean add(Integer e)
{
    int i = 0;
    for (Iterator<Integer> it = this.iterator(); it.hasNext();)
    {
        int current = it.next();
        if(current > e)
        {
            super.add(i, e);
            return true;
        }
        i++;
    }
    return super.add(e);
}

Le code ci-dessus crée une liste triée d'entiers, toujours triée. Il peut facilement être modifié pour fonctionner avec tout autre type de données. Cependant, dans ce cas, vous devrez éviter d'utiliser la fonction add(index, value), car cela romprait évidemment le tri.

Bien que les personnes ci-dessus aient suggéré d'utiliser Arrays.sort (), j'éviterais cela, car l'approche peut être nettement moins efficace, d'autant plus que la méthode de tri doit être appelée à chaque ajout à la liste.

0
Varun Madiath

Le contrat de l'interface ListIterator est un peu lourd, mais cette méthode effectuera l'insertion à l'aide d'un seul balayage de la liste (jusqu'au point d'insertion):

private void add(Integer value) {
    ListIterator<Integer> listIterator = list.listIterator();

    Integer next = null;

    while (listIterator.hasNext()) {
        next = listIterator.next();

        if (next.compareTo(value) > 0) {                
            break;
        }
    }

    if (next == null || next.compareTo(value) < 0) {
        listIterator.add(value);
    } else {
        listIterator.set(value);
        listIterator.add(next);
    }
}
0
Greg Brown