【问题标题】:Java List Sorting: Is there a way to keep a list permantly sorted automatically like TreeMap?Java 列表排序:有没有办法让列表像 TreeMap 一样自动永久排序?
【发布时间】:2011-02-04 22:36:29
【问题描述】:

在 Java 中,您可以使用项目构建 ArrayList,然后调用:

Collections.sort(list, comparator);

有没有在列表时传入比较器,创建就像你可以用TreeMap 做的那样?

目标是能够将一个元素添加到列表中,而不是将其自动附加到列表的末尾,列表将根据Comparator 保持自身排序并在确定的索引处插入新元素通过Comparator。所以基本上列表可能必须根据添加的每个新元素重新排序。

有没有办法以这种方式使用Comparator 或其他类似的方式来实现?

【问题讨论】:

  • @对于所有提供 PriorityQueue 的人,它是通过 Heap [由数组支持] 实现的,因此它在自然顺序上不是 Iterable,而是堆“构建”顺序。对堆的每个操作都需要向上/向下筛选。
  • 如果您不需要任何List 功能,请使用TreeSet
  • 这样的事情会破坏List 的合同,因为List 操作指定在哪里 他们添加元素...add(E) 添加到结束add(int, e) 在指定索引处添加等。set(int, E) 会更奇怪。
  • 虽然在技术上是正确的,但我认为违反add(e) 的合同并不是什么大不了的事。您可能希望为 add(int, e)set(int, e) 抛出异常以及其他没有意义的异常。
  • @Eric:如果这个List 可以被任何期望List 不违反其合同的代码使用,那将是一件大事。

标签: java list sorting collections treemap


【解决方案1】:

你可以改变 ArrayList 的行为

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

注意:PriorityQueue 不是 List,如果您不关心它是什么类型的集合,最简单的方法是使用 TreeSet,它就像 TreeMap 但它是一个集合。 PriorityQueue 的唯一优势是允许重复。

注意:对于大型集合而言,重新排序不是很有效,使用二分查找和插入条目会更快。 (但更复杂)

编辑:很大程度上取决于您需要“列表”做什么。我建议您为 ArrayList、LinkedList、PriorityQueue、TreeSet 或其他排序集合之一编写一个 List 包装器,并实现将实际使用的方法。这样您就可以很好地了解集合的要求,并确保它适合您。

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

【讨论】:

  • +1,一个简单有效的解决方案。只是,返回super.add(..) 的结果可能比总是返回true 更好。虽然在这种情况下它并不重要,因为ArrayList 也硬编码了return true
  • PriorityQueue 有更多优势:它在大小和速度方面肯定要高效得多。
  • 像这样通过 Collections.sorts 排序效率很低,调用 toArray,克隆数组,然后通过 ListIterator 使用集合。在这方面,使用 LinkedList 并在正确的位置插入是一个更好的选择。
  • 这个解决方案对于 n 次插入来说是 O(n^2 * log(n))。
  • @PeterLawrey 一如既往,您的解决方案非常棒!你应该在谷歌或甲骨文工作(如果你还没有)。
【解决方案2】:

大家都在推荐PriorityQueue。但是,重要的是要意识到,如果您 iterate 覆盖了 PriorityQueue 的内容,则元素将不会按排序顺序。您只能保证从 peek()poll() 等方法中获取“最小”元素。

TreeSet 似乎更合适。需要注意的是,作为Set,它不能包含重复的元素,也不支持使用索引进行随机访问。

【讨论】:

  • 您可以使用迭代器模拟随机访问。对于 LinkedList 来说,情况不会更糟。使用专有实现可以在O(log(size)) 中完成,请参阅我的答案。
  • 你也可以使用SkipList。
【解决方案3】:

SortedSet 和 List 的主要区别在于:

  • SortedSet 将其元素保持在正确的顺序,但您不能真正通过索引访问特定元素。
  • List 允许索引访问和元素的任意顺序。它还允许将任何元素(通过索引或迭代器)更改为另一个元素,而不会更改位置。

您似乎想要两者的融合:自动排序和允许(合理的快速)索引访问。根据数据的大小以及索引读取或添加新元素的频率,我的想法如下:

  • 一个包装好的 ArrayList,其中 add 方法使用 ListIterator 查找插入点,然后将元素插入那里。插入是 O(n),索引访问是 O(1)。
  • 一个包装好的 LinkedList,其中 add 方法使用 ListIterator 来查找插入点,然后将元素插入那里。 (对于插入(有时像 ArrayList 一样小,有时甚至更多)以及索引访问,这仍然是 O(n)。)
  • 一个修改过的二叉树跟踪每一层上两半的大小,从而启用索引访问。 (每次访问都需要 O(log n),但需要一些额外的编程,因为它还没有包含在 Java SE 中。或者你可以找到一些可以做到这一点的库。)

在任何情况下,SortedSet 和 List 的接口和协定并不真正兼容,因此您希望 List 部分是只读的(或只读和删除),不允许设置和添加,并且有一个额外的对象(可能实现 Collection 接口)来添加对象。

【讨论】:

    【解决方案4】:

    具有更有效随机访问的 TreeSet(或 TreeMultiset,以防您需要重复)是可能的,但我怀疑它是用 Java 实现的。让树的每个节点都记住其左子树的大小允许按索引及时访问元素O(log(size)),这还不错。

    为了实现它,您需要重写底层 TreeMap 的大部分内容。

    【讨论】:

    【解决方案5】:

    我会使用 Guava TreeMultiset 假设你想要一个 List 因为你可能有重复的元素。它会做你想做的一切。它没有的一件事是基于索引的访问,这没有多大意义,因为无论如何您都没有将元素放在您选择的索引处。要注意的另一件事是它实际上不会存储 equal 对象的重复项......只是对它们总数的计数。

    【讨论】:

      【解决方案6】:

      我还发现 Java 标准库中不存在这令人难以置信。 (但祝你好运,提议向 JDK 团队添加任何新类!我从来没有这么幸运过。)

      假设您的 compareTo 函数是适当的传递关系,那么最快的算法(假设列表的读取次数大约与写入次数相同)是使用执行二进制的方法覆盖 List.add在插入之前搜索以找到新项目的插入点。这是添加元素数量的 O(log(N))。

      【讨论】:

        【解决方案7】:

        在 JavaFX TransformationList 层次结构中,有一个称为 SortedList 的东西。该列表是完全可观察的,因此添加/删除将通知任何其他正在查看该列表的听众。

        执行此操作的基本方法是您观察另一个 ObservableList 的更改并战略性地使用 Collections.binarySearch(),因为其他人建议在 Olog(n) 时间内定位添加或删除的索引.

        这里有一个我没有看到的问题,那就是能够跟踪添加的具有相同 compareTo 签名的项目,即 T1.compareTo(T2) == 0。在此如果排序列表(我将在下面发布我自己的源代码)必须有一个包装元素类型,我将称之为 Element。这类似于 JavaFX 中的创建者对 SortedList 所做的。其原因完全是由于移除操作,如果存在 compareTo 重复,则无法定位到原始元素。通常在像 TreeSet 这样的 NavigableSet 实现中,这些重复项永远不会进入 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 ....}
        

        所有绑定类型(Sort、Distinct、Map、FlatMap 等)的抽象基类

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

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

        仅用于测试的包装类

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

        【讨论】:

        • “JUnit Benchmark Test”的结果如下:
        • 树集:330 毫秒
        • 绑定:16611 毫秒
        • JavaFX 排序列表:165826 毫秒
        • 参数:随机添加 200,000 个(非重复)随机 IntWrapper 测试项到以下每个:ListContentSortBinding(我的类)、SortedList(JavaFX)和 TreeSet。
        【解决方案8】:

        commons-collections 有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 来验证集合是否未更改。

        根据用例,这可能比 Peter 的建议效率低或高。例如,如果您添加多个项目并进行迭代。 (在迭代之间不添加项目),那么这可能会更有效。

        【讨论】:

        • PriorityQueue 的迭代确实保持顺序,对List 进行排序的最常见原因是以特定的排序顺序对其进行iterate。跨度>
        【解决方案9】:

        我相信Priority Queue 可以胜任。

        警告(来自同一文档页面):

        这个类和它的迭代器实现 的所有可选方法 Collection 和迭代器接口。 方法中提供的Iterator iterator() 不保证 遍历优先级的元素 以任何特定顺序排队。如果你 需要有序遍历,考虑使用 Arrays.sort(pq.toArray()).

        【讨论】:

        • PriorityQueue 的迭代确实保持顺序,而对List 进行排序的最常见原因是以特定的排序顺序对其进行iterate。跨度>
        • 基于 OP 未明确声明的假设而投反对票?好的。无论如何添加警告......
        • PriorityQueue 确实没有实现List 接口。 OP 明确声明他们想要List
        【解决方案10】:

        评论

        JDK 中没有SortedList 实现可能是有充分理由的。我个人想不出在 JDK 中有一个自动排序的理由。

        感觉过早的优化出了问题。如果列表的读取频率不如插入的频率高,那么您将无缘无故地浪费循环排序。在读取之前进行排序会更加反应性,并且在某处有一个boolean 表示列表在读取之前需要或不需要进行排序会更好。

        问题是您只在使用Iteratorfor 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,因为使用“列表”可以获得更好的性能特征。

        这是一个简单的例子:

        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 以及如果是这种情况该怎么办,但这给了你想法。您仍然没有任何确定性的方法来处理重复项。

        番石榴解决方案:

        如果您使用 Guava 并且应该使用,则可以使用

        Ordering.immutableSortedCopy() 仅当您需要迭代并完成它时。

        【讨论】:

        • 为什么要在添加时调用排序,在正确的位置插入要好得多?
        • 因为 OP 想要那样做,也许他们不知道“正确”位置是什么,这就是 Comparator 的用途,以确定所有项目的“正确”位置相对于其他项目。
        • 有序数据结构是计算机科学的基本组成部分。奇怪的是,你想不出它们的单一用例。
        • @AryaPourtabatabaie - 阅读理解,这不是关于不需要排序结构,而是关于 自动排序列表 作为通用结构不是一个好主意,而不是在 JDK 中。
        【解决方案11】:

        显而易见的解决方案是创建自己的类来实现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

        【讨论】:

          【解决方案12】:

          在添加/indexOf/remove/get 元素的时间少于 O(n) 的时间内拥有任何排序结构的唯一方法是使用树。在那种情况下,操作通常有 O(log2n) 并且遍历就像 O(1)。

          O(n) 只是一个链表。


          编辑:插入带二分查找的链表。对于插入操作,不使用二进制结构,而且尺寸不小,应该是最优的。

          @彼得: 有算法 w/ O(log2n) 比较(很慢)插入和 O(n) 移动。 如果您需要覆盖 LinkedList,那就这样吧。但这是尽可能整洁的。我保持算法尽可能简洁,易于理解,可以稍微优化一下。

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

          【讨论】:

            【解决方案13】:

            考虑一下我在面临类似问题时创建的indexed-tree-map,您将能够通过索引访问元素并获取元素的索引,同时保持排序顺序。可以将重复项作为同一键下的值放入数组中。

            【讨论】:

              【解决方案14】:

              最好的方法是覆盖列表的 add 实现。 我将使用 LinkedList 来演示它,因为它允许高效插入。

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

              上面的代码创建了一个排序的整数列表,它总是排序的。可以轻松修改它以使用任何其他数据类型。但是在这里你必须避免使用add(index, value) 函数,因为这显然会破坏排序。

              尽管上面的人建议使用 Arrays.sort(),但我会避免这样做,因为它可能是一种效率显着降低的方法,尤其是在每次添加到列表时都必须调用排序方法。

              【讨论】:

              • 是的,如果我要使用 ArrayList,我肯定会使用 Binarysearch。但是,我选择使用 LinkedList,因为我要进行高效插入。如果高效遍历很重要,我会采取不同的做法。
              • 这个解决方案是 O(n^2) 插入 n 个元素。
              • 是的,如果 arrays.sort 使用插入排序,则为 O(n^3),如果使用快速排序,则为 O(n^2 log n)。
              • 这些比较是正确的,除了两件事。 1:java.util.Arrays.sort 和 java.util.Collections.sort 都保证 O(nlog(n)) 行为。 2:正确的比较是vs自平衡二叉搜索树,它给了O(nlog(n))total来插入n个元素。
              • 迭代器的使用很好,但是这个解决方案仍然需要对列表进行 2 次扫描:一次查找插入点,然后另一次执行插入。使用 ListIterator 会更有效。我将发布一个示例。
              【解决方案15】:

              ListIterator 接口的约定使得它有点麻烦,但是这种方法将使用列表的单次扫描(直到插入点)来执行插入:

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

              【讨论】:

                【解决方案16】:

                SortedSet

                SortedSet 接口的任何实现都带有您想要的行为。

                默认情况下,添加的对象按其自然顺序排序,即基于它们对Comparable::compareTo 接口方法的实现。

                或者,您可以传递Comparator 实现来确定排序。

                TreeSet

                TreeSetSortedSet的常用植入。你也可以找其他人。

                重复

                ListSortedSet 之间的主要区别在于重复,即比较相等的对象。 List 允许重复,而 SortedSet 与任何 Set 一样,不允许重复。

                按索引访问

                另一个区别是Set 不能通过索引访问。您无法通过对象在集合中的位置编号来定位对象。

                如果您在构建 SortedSet 后需要此类访问权限,请创建 List。有多种方法可以做到这一点,例如将SortedSet 传递给ArrayList 的构造函数。从 Java 10 开始,最近的一种方法是通过将 SortedSet 传递给 List.copyOf 来生成不可修改的 List

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2019-07-08
                  • 1970-01-01
                  • 2021-01-28
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-01-08
                  • 2012-11-19
                  • 2019-11-15
                  相关资源
                  最近更新 更多