it-swarm.com.de

Java List Sorting: Gibt es eine Möglichkeit, eine Liste wie TreeMap automatisch dauerhaft zu sortieren?

In Java können Sie eine ArrayList mit Elementen erstellen und dann Folgendes aufrufen:

Collections.sort(list, comparator);

Gibt es zum Zeitpunkt der Listenerstellung eine Möglichkeit, den Comparator zu übergeben, wie dies mit TreeMap möglich ist? Das Ziel ist, ein Element zur Liste hinzufügen zu können. Anstatt es automatisch an das Ende der Liste anzuhängen, wird die Liste nach dem Komparator sortiert und das neue Element an der vom Komparator festgelegten Stelle eingefügt. Daher muss die Liste möglicherweise nach jedem neu hinzugefügten Element neu sortiert werden.

Gibt es überhaupt eine Möglichkeit, dies mit dem Komparator oder auf ähnliche Weise zu erreichen?

30
Dave L.

Sie können das Verhalten von ArrayList ändern

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

Hinweis: Eine PriorityQueue ist KEINE Liste. Wenn Sie sich nicht für die Art der Sammlung interessieren, ist es am einfachsten, ein TreeSet zu verwenden, das wie eine TreeMap ist, aber eine Sammlung ist. Der einzige Vorteil von PriorityQueue besteht darin, Duplikate zuzulassen.

Hinweis: Das Umsortieren ist für große Sammlungen nicht sehr effizient. Die Verwendung einer binären Suche und das Einfügen eines Eintrags wäre schneller. (aber komplizierter)

BEARBEITEN: Viel hängt davon ab, was Sie für die "Liste" benötigen. Ich schlage vor, Sie schreiben einen List-Wrapper für eine ArrayList, LinkedList, PriorityQueue, TreeSet oder eine der anderen sortierten Sammlungen und implementieren die Methoden, die tatsächlich verwendet werden. Auf diese Weise haben Sie ein gutes Verständnis für die Anforderungen an die Sammlung und können sicherstellen, dass sie für Sie richtig funktioniert.

EDIT (2): Da es so viel Interesse gab, stattdessen binarySearch zu verwenden. ;)

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

Jeder schlägt PriorityQueue vor. Es ist jedoch wichtig zu wissen, dass, wenn Sie iterieren über den Inhalt einer PriorityQueue gehen, die Elemente nicht in sortierter Reihenfolge sind. Das Element "minimum" wird nur von den Methoden peek(), poll() usw. garantiert.

Eine TreeSet scheint besser zu passen. Die Vorbehalte wären, dass es als Set keine doppelten Elemente enthalten kann und keinen wahlfreien Zugriff mit einem Index unterstützt.

14
erickson

Kommentar

Es gibt wahrscheinlich einen guten Grund dafür, dass es im JDK keine SortedList-Implementierung gibt. Ich persönlich kann mir keinen Grund für eine automatische Sortierung im JDK vorstellen.

Es riecht nach verfrühter Optimierung. Wenn die Liste nicht so oft gelesen wird, wie sie eingefügt wird, verschwenden Sie das Sortieren von Zyklen ohne Grund wiederholt. Direkt vor dem Lesen zu sortieren, wäre viel mehr reaktiv und eine boolean-Position irgendwo gibt an, dass die Liste vor dem Lesen sortiert werden muss oder nicht, dann wäre sie noch besser.

Die Sache ist nur, dass Sie sich wirklich nur um die Reihenfolge kümmern, wenn Sie die Liste mit einer Iterator- oder for each-Schleife durchlaufen. Der Aufruf von Collections.sort() vor einem durchlaufenen Code wäre wahrscheinlich performanter als der Versuch, die Liste bei jeder Einfügung ständig sortiert zu halten.

Es gibt Unklarheiten mit List aufgrund von Duplikaten. Wie ordnen Sie Duplikate deterministisch an? Es gibt SortedSet, was wegen der Einzigartigkeit sinnvoll ist. Das Sortieren einer List kann jedoch mehr Komplikationen aufgrund der Nebenwirkungen von Duplikaten und anderen Einschränkungen haben, z. B. wenn Sie jedes Objekt Comparable machen oder wenn ich in meinem Code zeige, dass er eine Comparator braucht, die die Arbeit erledigen kann.

Sortierung nach .add()

Wenn Sie eine sehr spezielle Situation haben, in der eine automatische Sortierung List nützlich wäre, könnten Sie eine Unterklasse einer List-Implementierung und eine Überschreibung .add() ausführen, um eine Collections.sort(this, comparator) auszuführen, die Sie an einen benutzerdefinierten Konstruktor übergeben. Ich habe LinkedList anstelle von ArrayList für einen Grund verwendet, ArrayList ist eine natürliche Reihenfolge der Einfügungssortierung List. Es hat auch die Fähigkeit, .add() an einem Index zu verwenden, der ziemlich nutzlos ist, wenn Sie eine ständig sortierte List wünschen, die in irgendeiner Weise gehandhabt werden müsste, die wahrscheinlich nicht ideal wäre. Laut dem Javadoc;

void    add(int index, Object element)

Fügt das angegebene Element an der .__ ein. angegebene Position in dieser Liste (optionale Operation).

Es wäre also akzeptabel, UnSupportedOperationException zu werfen, oder Sie könnten die index einfach ignorieren und an .add(Object element); delegieren, wenn Sie sie in einem JavaDoc für die Methode dokumentieren.

Normalerweise, wenn Sie viele Einfügungen/Entfernungen und Sortierungen durchführen möchten, würden Sie aufgrund der besseren Leistungseigenschaften aufgrund der Verwendung der `Liste 'eine LinkedList verwenden.

Hier ein kurzes Beispiel:

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

Effizienteste Lösung:

Alternativ können Sie nur sortieren, wenn Sie die Variable Iterator erhalten. Dies wäre leistungsorientierter, wenn die sortierte Reihenfolge nur beim Iterieren über die Variable List wirklich wichtig ist. Dies würde den Anwendungsfall abdecken, in dem der Clientcode vor jeder Iteration Collections.sort() nicht aufgerufen werden muss und dieses Verhalten in die Klasse einkapselt.

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

Natürlich müsste es eine Fehlerprüfung und -behandlung geben, um zu sehen, ob die Comparatornull war oder nicht und was zu tun ist, wenn dies der Fall war, aber dies gibt Ihnen die Idee. Sie haben noch keine deterministische Methode, um mit Duplikaten umzugehen.

Guavenlösung:

Wenn Sie Guave verwenden, sollten Sie dies auch tun

Ordering.immutableSortedCopy() Nur wenn Sie iterieren müssen und fertig sind.

5
user177800

Etwas wie TreeSet (oder TreeMultiset, falls Du Duplikate benötigst) mit effizienterem Direktzugriff ist möglich, aber ich bezweifle, dass es in Java implementiert wurde. Wenn Sie für jeden Knoten des Baums die Größe seines linken Teilbaums festlegen, können Sie mit der Zeit O(log(size)) auf ein Element zugreifen, das nicht schlecht ist.

Um dies zu implementieren, müssen Sie einen guten Teil der zugrunde liegenden TreeMap umschreiben.

4
maaartinus

Ich würde ein GuavaTreeMultiset verwenden, vorausgesetzt, Sie möchten eine List, weil Sie möglicherweise doppelte Elemente haben. Es wird alles tun, was du willst. Das einzige, was es nicht gibt, ist ein indexbasierter Zugriff, der wenig sinnvoll ist, da Sie sowieso keine Elemente auf Indizes Ihrer Wahl setzen. Die andere Sache, die Sie beachten sollten, ist, dass es keine Duplikate von equal-Objekten speichert ... nur eine Zählung der Gesamtanzahl von ihnen.

2
ColinD

commons-Sammlungen haben TreeBag

Anfangs schlug ich PriorityQueue vor, aber die Iterationsreihenfolge ist undefiniert. Daher ist es sinnlos, es sei denn, Sie iterieren den Kopf eines Klons der Warteschlange, bis er leer ist.

Da Sie höchstwahrscheinlich mit der Iterationsreihenfolge befasst sind, können Sie die iterator()-Methode überschreiben:

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

Sie können dies verbessern, indem Sie eine Momentaufnahme der sortierten Sammlung speichern und mit modCount überprüfen, ob die Sammlung nicht geändert wird.

Abhängig von den Anwendungsfällen kann dies weniger oder effizienter sein als der Vorschlag von Peter. Zum Beispiel, wenn Sie mehrere Elemente hinzufügen und iterieren. (ohne Elemente zwischen Iterationen hinzuzufügen), ist dies möglicherweise effizienter.

2
Bozho

Betrachten Sie indexed-tree-map , das ich erstellt habe, während Sie ein ähnliches Problem hatten, können Sie auf Elemente über Index zugreifen und einen Index von Elementen abrufen, während Sie die Sortierreihenfolge beibehalten. Duplikate können als Werte unter demselben Schlüssel in Arrays eingefügt werden. 

1

Die einzige Möglichkeit, eine sortierte Struktur mit weniger als O(n) Zeit für das Hinzufügen des/indexOf/remove/get -Elements zu haben, besteht in der Verwendung einer Baumstruktur. In diesem Fall haben Operationen im Allgemeinen O(log2n) und das Durchlaufen ist wie O (1).

O (n) ist nur eine verknüpfte Liste.


Bearbeiten: Einfügen in eine verknüpfte Liste mit binärer Suche. Für Einfügeoperationen, bei denen keine binäre Struktur verwendet wird und keine kleinen Größen, sollte dies optimal sein.

@Peter: Es gibt den Algorithmus w/O(log2n) vergleicht (was langsam ist) zum Einfügen und O(n) bewegt . Wenn Sie müssen überschreiben Sie LinkedList, also sei es. Aber das ist so ordentlich wie es nur geht. Ich halte den Algorithmus so sauber wie möglich, um leicht verständlich zu sein, er kann etwas optimiert werden.

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

In der JavaFX TransformationList-Hierarchie gibt es eine SortedList. Die Liste ist vollständig beobachtbar, so dass alle Zuhörer, die die Liste betrachten, durch Hinzufügungen/Entfernungen benachrichtigt werden.

Der grundlegende Ansatz dafür ist, dass Sie eine andere ObservableList auf Änderungen überprüfen und Collections.binarySearch () strategisch verwenden, da andere vorgeschlagen haben, den Index der Hinzufügung oder Entfernung in Olog (n) time zu lokalisieren.

Es gibt ein Problem, das ich hier nicht gesehen habe, und das ist die Möglichkeit, Elemente mit derselben compareTo - Signatur zu verfolgen, dh T1.compareTo (T2) == 0. In diesem Fall ist die sortierte Liste (I wird meinen eigenen Quellcode bereitstellen) muss einen Wrapper-Elementtyp haben, den ich Element nennen werde. Dies ist ähnlich wie bei den Erstellern in JavaFX mit SortedList . Der Grund dafür ist vollständig auf Entfernungsvorgänge zurückzuführen. Das Originalelement kann nicht gefunden werden, wenn CompareTo-Duplikate vorhanden sind. Normalerweise würden in einer NavigableSet -Implementierung wie TreeSet diese Duplikate niemals das Set betreten. Eine Liste ist anders.

Ich habe eine Bibliothek mit beobachtbaren Listen, die effektiv miteinander verkettet werden können (sehr ähnlich zu Java-Streams) und die Ergebnisse stromabwärts vollständig als vorherige Quelle in den Kettenaktualisierungen verbreiten.

Klassenhierarchie

Schnittstelle

/**
 * 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 ....}

Abstrakte Basisklasse für alle Bindungstypen (Sortieren, Distinct, Map, FlatMap usw.)

/**
 * 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 ....}

Bindungsklasse sortieren

/**
 * 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];
    }

Wrapper-Elementklasse

    /**
     * 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 + ")";
        }
    }
}

JUNIT VERIFICATION TEST

@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.");
}

JUNIT BENCHMARK TEST

  @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");
}

Wrapper-Klasse nur für Tests

    /**
     * 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

Die naheliegendste Lösung besteht darin, eine eigene Klasse zu erstellen, die die Java.util.List-Schnittstelle implementiert und dem Konstruktor eine Comparator als Argument verwendet. Sie würden den Komparator an den richtigen Stellen verwenden, d. H. Die add-Methode würde die vorhandenen Elemente durchlaufen und das neue Element an der richtigen Stelle einfügen. Sie würden Aufrufe von Methoden wie add(int index, Object obj) usw. nicht zulassen.

In der Tat muss dies bereits jemand erstellt haben ... eine schnelle Google-Suche zeigt mindestens ein Beispiel:

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

1
Eric Giguere

Der Hauptunterschied zwischen SortedSet und List ist:

  • SortedSet behält das Element in der richtigen Reihenfolge, aber Sie können auf ein bestimmtes Element nicht wirklich über den Index zugreifen.
  • List ermöglicht den indizierten Zugriff und die willkürliche Anordnung der Elemente. Es erlaubt auch, jedes Element (nach Index oder Iterator) in ein anderes Element zu ändern, ohne dass sich die Position ändert.

Sie scheinen eine Mischung aus beidem zu wollen: automatische Sortierung und Ermöglichung eines (angemessenen) schnellen Indexzugriffs. Je nach Größe der Daten und wie oft indexiertes Lesen oder Hinzufügen neuer Elemente auftritt, sind dies meine Ideen:

  • eine umschlossene ArrayList, bei der die add-Methode einen ListIterator verwendet hat, um den Einfügepunkt zu finden und das Element dort einzufügen. Dies ist O(n) für Einfügungen, O(1) für indizierten Zugriff.
  • eine umhüllte LinkedList, bei der die add-Methode einen ListIterator verwendet hat, um den Einfügepunkt zu finden und das Element dort einzufügen. (Dies ist immer noch O(n) für Einfügungen (mit zum Teil recht kleinerem Faktor als ArrayList, manchmal sogar mehr) sowie zum indizierten Zugriff.)
  • ein modifizierter Binärbaum, der die Größen beider Hälften auf jeder Ebene verfolgt und somit den indizierten Zugriff ermöglicht. (Dies wäre O (log n) für jeden Zugriff, erfordert jedoch zusätzliche Programmierung, da es noch nicht in der Java SE enthalten ist. Oder Sie finden eine Bibliothek, die dies ermöglicht.)

In jedem Fall sind die Schnittstellen und Verträge von SortedSet und List nicht wirklich kompatibel. Sie möchten also, dass der List-Teil schreibgeschützt ist (oder nur Lesen und Löschen) und das Setzen und Hinzufügen nicht zulässt object (möglicherweise implementiert die Collection-Schnittstelle) zum Hinzufügen von Objekten.

1
Paŭlo Ebermann

SortedSet

Jede Implementierung der SortedSet -Schnittstelle trägt Ihr gewünschtes Verhalten. 

Standardmäßig werden hinzugefügte Objekte in ihrer natürlichen Reihenfolge sortiert, dh basierend auf ihrer Implementierung der Comparable::compareTo interface-Methode.

Alternativ können Sie eine Comparator -Implementierung übergeben, um die Sortierung zu bestimmen. 

TreeSet

Die TreeSet ist eine häufig verwendete Implantation von SortedSet. Sie können auch andere finden. 

Duplikate

Der Hauptunterschied zwischen einer List und einer SortedSet besteht in Duplikaten, Objekten, die als gleich betrachtet werden. Eine List erlaubt Duplikate, eine SortedSet, wie jede Set, nicht. 

Zugriff über Index

Ein weiterer Unterschied besteht darin, dass auf eine Set kein Index zugreifen kann. Sie können ein Objekt nicht anhand seiner Positionsnummer in der Sammlung lokalisieren. 

Wenn Sie nach dem Erstellen Ihrer SortedSet einen solchen Zugriff benötigen, nehmen Sie eine List vor. Dazu gibt es mehrere Möglichkeiten, beispielsweise die SortedSet an den Konstruktor von ArrayList übergeben. Ein neuerer Weg, seit Java 10, besteht darin, eine umodifizierbare List durch Übergeben der SortedSet an List.copyOf zu machen. 

0
Basil Bourque

Der beste Weg, dies zu tun, wäre, die Add-Implementierung einer Liste zu überschreiben. Ich werde eine LinkedList verwenden, um sie zu demonstrieren, da sie eine effiziente Einfügung ermöglicht.

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

Der obige Code erstellt eine sortierte Liste von Ganzzahlen, die immer sortiert wird. Es kann leicht geändert werden, um mit jedem anderen Datentyp zusammenzuarbeiten. Allerdings müssen Sie hier die Verwendung der Funktion add(index, value) vermeiden, da dies die Sortierung offensichtlich beeinträchtigen würde.

Obwohl die Leute oben die Verwendung von Arrays.sort () vorgeschlagen haben, würde ich dies vermeiden, da dies ein wesentlich weniger effizienter Ansatz sein kann, insbesondere da die Sortiermethode bei jedem Hinzufügen der Liste aufgerufen werden muss.

0
Varun Madiath

Der Vertrag der ListIterator-Schnittstelle macht es etwas umständlich, aber diese Methode führt die Einfügung mit einem einzigen Scan der Liste (bis zum Einfügepunkt) aus:

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