Сортировка списков Java: есть ли способ сохранить список автоматически отсортированным, как TreeMap? - PullRequest
32 голосов
/ 05 февраля 2011

В Java вы можете создать ArrayList с элементами, а затем вызвать:

Collections.sort(list, comparator);

Есть ли способ передать Comparator во время списка, создание, как вы можете сделать с TreeMap?

Цель состоит в том, чтобы иметь возможность добавить элемент в список, и вместо того, чтобы автоматически добавлять его в конец списка, список сохранял бы свою сортировку на основе Comparator и вставлял новый элемент виндекс определяется по Comparator.Таким образом, в основном список, возможно, придется пересортировать по каждому добавленному новому элементу.

Есть ли способ достичь этого таким способом с помощью Comparator или каким-либо другим аналогичным способом?

Ответы [ 15 ]

47 голосов
/ 05 февраля 2011

Вы можете изменить поведение ArrayList

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

Примечание: PriorityQueue НЕ является списком, если вам все равно, какой это тип коллекции, проще всего использовать TreeSet, который похож на TreeMap, но представляет собой коллекцию. Единственное преимущество PriorityQueue - разрешить дублирование.

Примечание: прибегание не очень эффективно для больших коллекций. Использование бинарного поиска и вставка записи будет быстрее. (но более сложный)

РЕДАКТИРОВАТЬ: многое зависит от того, что вам нужно сделать "список". Я предлагаю вам написать упаковщик List для ArrayList, LinkedList, PriorityQueue, TreeSet или одной из других отсортированных коллекций и реализовать методы, которые будут фактически использоваться. Таким образом, вы хорошо понимаете требования к коллекции и можете быть уверены, что она работает правильно для вас.

EDIT (2): Так как вместо этого был большой интерес к использованию binarySearch. ;)

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;
    }
};
15 голосов
/ 05 февраля 2011

Все предлагают PriorityQueue. Однако важно понимать, что если вы итерируете по содержимому PriorityQueue, элементы не будут в отсортированном порядке. Вы гарантированно получите «минимальный» элемент из методов peek(), poll() и т. Д.

A TreeSet кажется более подходящим. Предостережения заключаются в том, что, как Set, он не может содержать повторяющиеся элементы и не поддерживает произвольный доступ с индексом.

5 голосов
/ 05 февраля 2011

Комментарий

Вероятно, есть веская причина, по которой в JDK нет реализации SortedList.Лично я не могу придумать причину, по которой в JDK есть одна автосортировка.

Это пахнет преждевременной оптимизацией, которая пошла не так.Если список читается не так часто, как он вставляется в него, то вы теряете циклы сортировки повторно без какой-либо причины.Сортировка прямо перед чтением была бы намного более реактивной , а наличие boolean где-то указывает на то, что список нужно сортировать или не нужно сортировать перед чтением, что будет еще лучше.

дело в том, что вы действительно заботитесь о порядке только при обходе списка с циклом Iterator или for each, поэтому вызов Collections.sort() перед любым итеративным кодом, вероятно, будет более производительным, чем попытка постоянно сортировать список на каждомвставка.

Существуют неясности с List из-за дубликатов, как вы определяете дубликаты детерминистически?Существует SortedSet, и это имеет смысл из-за уникальности.Но сортировка List может иметь больше осложнений из-за побочных эффектов дубликатов и других ограничений, таких как создание каждого объекта Comparable или, как я показываю в моем коде, наличие Comparator, которое может выполнять эту работу.

Сортировка по .add()

Если у вас есть какая-то особенная ситуация, когда автосортировка List будет полезна, то одна вещь, которую вы можете сделать, это реализация подкласса List и чрезмерноеехать .add(), чтобы сделать Collections.sort(this, comparator), который вы передадите в пользовательский конструктор.Я использовал LinkedList вместо ArrayList по причине, ArrayList - это естественный порядок сортировки вставок List для начала.Он также имеет возможность .add() по индексу, который довольно бесполезен, если вы хотите постоянно сортировать List, который должен был бы быть обработан так или иначе, что, вероятно, было бы не идеально.Согласно Javadoc;

void    add(int index, Object element)

Вставляет указанный элемент в указанную позицию в этом списке ( необязательная операция ).

Так что было бы приемлемо просто выбросить UnSupportedOperationException, или вы могли бы просто проигнорировать index и делегировать .add(Object element);, если вы документируете его в JavaDoc по методу.

Обычно, когда выЕсли вам нужно много операций вставки / удаления и сортировки, вы бы использовали LinkedList из-за лучших характеристик производительности при использовании `List '.

Вот краткий пример:

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

Наиболее эффективное решение:

В качестве альтернативы вы могли бы сортировать только при получении Iterator, и это было бы более ориентировано на производительность, если бы отсортированный порядок был действительно важен только при итерации по List.Это будет охватывать сценарий использования клиентского кода, который не должен вызывать Collections.sort() перед каждой итерацией, и инкапсулировать это поведение в классе.

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

Конечно, потребуется проверка и обработка ошибок дляпосмотрите, был ли Comparator null или нет, и что делать, если это было так, но это дает вам идею.У вас до сих пор нет детерминированного способа борьбы с дубликатами.

Решение на основе гуавы:

Если вы используете гуаву и вам это следует, вы можете просто использовать

Ordering.immutableSortedCopy() только тогда, когда вам нужно выполнить итерацию и покончить с этим.

4 голосов
/ 05 февраля 2011

Возможно что-то вроде TreeSet (или TreeMultiset, если вам нужны дубликаты) с более эффективным произвольным доступом, но я сомневаюсь, что это было реализовано в Java.Заставив каждый узел дерева запоминать размер его левого поддерева, можно получить доступ к элементу по индексу за время O(log(size)), что неплохо.

Чтобы реализовать его, вам нужно переписать хорошую частьлежащего в основе TreeMap.

2 голосов
/ 05 февраля 2011

Я бы использовал Гуава TreeMultiset , предполагая, что вы хотите List, потому что у вас могут быть повторяющиеся элементы.Это будет делать все, что вы хотите.Единственное, чего у него не будет, - это индексный доступ, который не имеет особого смысла, учитывая, что вы все равно не помещаете элементы в индексы по вашему выбору.Еще одна вещь, о которой нужно знать, это то, что на самом деле она не будет хранить дубликаты equal объектов ... только счетчик их общего количества.

2 голосов
/ 05 февраля 2011

общих коллекций имеют TreeBag

Первоначально я предложил PriorityQueue, но его порядок итераций не определен, поэтому он бесполезен, если вы не выполните итерацию, получив голову клона очереди, пока она не опустеет.

Поскольку вы, скорее всего, озабочены порядком итераций, я думаю, вы можете переопределить метод 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)
    }
}

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

В зависимости от вариантов использования, это может быть менее или более эффективным, чем предложение Питера. Например, если вы добавляете несколько элементов и выполняете итерацию. (без добавления элементов между итерациями), тогда это может быть более эффективным.

1 голос
/ 28 августа 2018

В иерархии JavaFX TransformationList есть нечто, называемое SortedList. Список полностью доступен для наблюдения, так что добавления / удаления будут уведомлять всех других слушателей, наблюдающих за списком.

Основной подход к этому состоит в том, что вы наблюдаете за другим ObservableList для изменений и стратегически используете Collections.binarySearch () , поскольку другие предложили найти индекс добавления или удаления во время Olog (n).

Существует одна проблема, о которой я не упоминал, и которая заключается в возможности отслеживать добавленные элементы, имеющие одинаковую подпись compareTo , т.е. T1.compareTo (T2) == 0. В этом случае отсортированный список (я опубликую свой собственный исходный код ниже) должен иметь тип элемента оболочки, который я назову Элемент . Это похоже на то, что создатели в JavaFX сделали с SortedList . Причина этого полностью связана с операциями удаления, невозможно найти исходный элемент, если есть дубликаты сравнения. Обычно в реализации NavigableSet , такой как TreeSet, эти дубликаты никогда не попадут в Set. Список другой.

У меня есть библиотека наблюдаемых списков, которые можно эффективно объединить в цепочку (очень похоже на потоки Java), которые полностью распространяют результаты в нисходящем направлении, как предыдущий источник в цепочке обновлений.

Иерархия классов

Интерфейс

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

Абстрактный базовый класс для всех типов привязки (Сортировка, Разное, Карта, Плоская карта и т. Д.)

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

Класс связывания сортировки

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

@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
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 только для тестов

    /**
     * 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 голос
/ 11 февраля 2013

Рассмотрим indexed-tree-map , которую я создал, столкнувшись с аналогичной проблемой, вы сможете получить доступ к элементам по индексу и получить индекс элементов при сохранении порядка сортировки. Дубликаты могут быть помещены в массивы как значения под одним и тем же ключом.

1 голос
/ 05 февраля 2011

Основное различие между SortedSet и List:

  • SortedSet держит свой элемент в правильном порядке, но вы не можете получить доступ к определенному элементу по индексу.
  • Список позволяет индексированный доступ и произвольный порядок элементов. Это также позволяет изменять любой элемент (по индексу или итератору) на другой элемент, без изменения местоположения.

Похоже, вы хотите объединить как автоматическую сортировку, так и предоставление (достаточно быстрого) доступа к индексу. В зависимости от размера данных и от того, как часто происходит индексированное чтение или добавление новых элементов, это мои идеи:

  • обернутый ArrayList, где метод add использовал ListIterator, чтобы найти место вставки, а затем вставил туда элемент. Это O (n) для вставок, O (1) для индексированного доступа.
  • упакованный LinkedList, где метод add использовал ListIterator, чтобы найти место вставки, а затем вставил туда элемент. (Это все еще O (n) для вставок (иногда с довольно меньшим коэффициентом, как ArrayList, иногда даже больше), а также с индексированным доступом.)
  • модифицированное двоичное дерево, отслеживающее размеры обеих половинок на каждом уровне, что обеспечивает индексированный доступ. (Это будет O (log n) для каждого доступа, но требует некоторого дополнительного программирования, поскольку он еще не включен в Java SE. Или вы найдете некоторую библиотеку, которая может это сделать.)

В любом случае, интерфейсы и контракты SortedSet и List не являются действительно совместимыми, поэтому вы захотите, чтобы часть List была только для чтения (или только для чтения и удаления), не позволяя устанавливать и добавлять, и наличие дополнительного объекта (возможно, реализующего интерфейс Collection) для добавления объектов.

1 голос
/ 05 февраля 2011

Очевидное решение - создать собственный класс, который реализует интерфейс java.util.List и принимает Comparator в качестве аргумента для конструктора.Вы должны использовать компаратор в правильных местах, т.е. метод add будет перебирать существующие элементы и вставлять новый элемент в правильное место.Вы бы запретили вызовы таких методов, как add(int index, Object obj) и т. Д.

На самом деле, кто-то уже должен был создать это ... быстрый поиск в Google показывает хотя бы один пример:

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

...