Чому на Java немає SortedList?


502

У Java є SortedSetі SortedMapінтерфейси. Обидва належать до рамки колекцій Java і надають сортований спосіб доступу до елементів.

Однак, на моє розуміння, SortedListу Java немає . Ви можете використовувати java.util.Collections.sort()для сортування списку.

Будь-яка ідея, чому вона спроектована так?



6
тож який ваш очікуваний результат при вставці елемента в середині списку?
bestsss

4
@bestsss Цілком можливо було б мати клас SortedList, який не реалізує інтерфейс java.util.List. Прочитайте питання як запит, чому не існує структури даних, яка підтримує запитуваний функціонал. Не відволікайтеся на незначні деталі, такі як іменування.
Альдерат

@Alderath, звичайна структура для цього дерева (червоний / чорний, avl або btree) з додатковими попередніми / наступними посиланнями для підтримки замовлення. Я використовую аналогічну структуру червоний / чорний з попереднім / наступним посиланнями. Хоча це цілком ніша використання. Дерево можна переміщувати як впорядкований, так і впорядкований порядок, має O (logn) знайти / містить, але get (int) є O (n). Враховуючи той факт, що його ніша застосовна, я б припустив, що розробникам було надано їх впровадити, якщо вони знадобляться.
bestsss

3
Не відповідаючи на питання "чому", але найпростішим способом вирішення для TreeSet є використання компаратора, який ніколи не повертає нуль, наприклад, int diff = this.score - that.score; return (diff == 0) ? 1 : diff; оскільки це смердючий злом, я б надавав це як аргумент анонімного конструктора, а не як будь-який реалізатор порівняльний.
вушка

Відповіді:


682

Ітератори списку гарантують насамперед, що ви отримаєте елементи списку у внутрішньому порядку списку (він же. Порядок вставки ). Більш конкретно, це в порядку, коли ви вставили елементи, або в тому, як ви маніпулювали списком. Сортування можна розглядати як маніпулювання структурою даних, і існує кілька способів сортування списку.

Я замовляю способи в порядку корисності, як я особисто бачу це:

1. Розгляньте натомість використання Setабо Bagколекції

ПРИМІТКА. Цей варіант я ставлю вгорі, тому що це, як правило, ви хочете зробити в будь-якому випадку.

Відсортований набір автоматично сортує колекцію при вставці , тобто означає, що вона виконує сортування під час додавання елементів у колекцію. Це також означає, що вам не потрібно вручну сортувати їх.

Крім того, якщо ви впевнені, що вам не потрібно турбуватися про (або мати) дублікати елементів, то ви можете використовувати TreeSet<T>замість цього. Він реалізує SortedSetта NavigableSetінтерфейси та працює так, як ви, напевно, очікували зі списку:

TreeSet<String> set = new TreeSet<String>();
set.add("lol");
set.add("cat");
// automatically sorts natural order when adding

for (String s : set) {
    System.out.println(s);
}
// Prints out "cat" and "lol"

Якщо ви не хочете природного впорядкування, ви можете скористатися параметром конструктора, який приймає a Comparator<T>.

Крім того, ви можете використовувати мультисети (також відомі як Bags ) , тобто, Setщо дозволяє повторювати елементи, а натомість є сторонні реалізації. Найбільш помітно з бібліотек Guava є такий TreeMultiset, який працює дуже схоже на TreeSet.

2. Сортуйте свій список Collections.sort()

Як було сказано вище, сортування Lists - це маніпулювання структурою даних. Тож для ситуацій, коли вам потрібно "одне джерело істини", яке буде сортоване різними способами, а потім їх сортування вручну - це шлях.

Ви можете сортувати свій список java.util.Collections.sort()методом. Ось зразок коду про те, як:

List<String> strings = new ArrayList<String>()
strings.add("lol");
strings.add("cat");

Collections.sort(strings);
for (String s : strings) {
    System.out.println(s);
}
// Prints out "cat" and "lol"

Використання компараторів

Очевидною перевагою є те, що ви можете використовувати Comparatorв sortметоді. Java також надає деякі реалізації для Comparatorтаких, Collatorякі корисні для чутливих до локалізації рядків. Ось один із прикладів:

Collator usCollator = Collator.getInstance(Locale.US);
usCollator.setStrength(Collator.PRIMARY); // ignores casing

Collections.sort(strings, usCollator);

Сортування в одночасних середовищах

Зауважте, що використання sortметоду не є дружнім у паралельних середовищах, оскільки екземпляр колекції буде маніпулювати, і вам слід замість цього використати незмінні колекції. Це те, що Guava забезпечує у Orderingкласі та є простим однолінійним:

List<string> sorted = Ordering.natural().sortedCopy(strings);

3. Обгорніть свій список java.util.PriorityQueue

Хоча в Java немає відсортованого списку, однак є відсортована черга, яка, ймовірно, спрацювала б так само добре для вас. Це java.util.PriorityQueueклас.

Ніко Хааз зв'язав у коментарях відповідне питання, яке також відповідає на це.

У відсортованій колекції ви, швидше за все, не хочете маніпулювати внутрішньою структурою даних, через що PriorityQueue не реалізує інтерфейс списку (тому що це надасть вам прямий доступ до його елементів).

Накладіть на PriorityQueueітератор

У PriorityQueueкласі реалізує Iterable<E>і Collection<E>інтерфейси , тому він може бути ітерованих як зазвичай. Однак ітератор не гарантує повернення елементів у відсортованому порядку. Натомість (як в коментарях вказує Альдерат), вам потрібно буде poll()в черзі, поки вона не порожня.

Зауважте, що ви можете перетворити список у чергу пріоритету через конструктор, який приймає будь-яку колекцію :

List<String> strings = new ArrayList<String>()
strings.add("lol");
strings.add("cat");

PriorityQueue<String> sortedStrings = new PriorityQueue(strings);
while(!sortedStrings.isEmpty()) {
    System.out.println(sortedStrings.poll());
}
// Prints out "cat" and "lol"

4. Напишіть власний SortedListклас

ПРИМІТКА. Вам цього не слід було робити.

Ви можете написати власний клас List, який сортує кожного разу, коли ви додаєте новий елемент. Це може бути досить важким для обчислень залежно від вашої реалізації та безглуздо , якщо ви не хочете робити це як вправу через дві основні причини:

  1. Він порушує контракт, який List<E>має інтерфейс, оскільки addметоди повинні гарантувати, що елемент буде перебувати в індексі, який вказує користувач.
  2. Навіщо винаходити колесо? Ви повинні використовувати TreeSet або Multisets замість цього, як зазначено в першій точці вище.

Однак якщо ви хочете зробити це як вправу, тут є зразок коду для початку роботи, він використовує AbstractListабстрактний клас:

public class SortedList<E> extends AbstractList<E> {

    private ArrayList<E> internalList = new ArrayList<E>();

    // Note that add(E e) in AbstractList is calling this one
    @Override 
    public void add(int position, E e) {
        internalList.add(e);
        Collections.sort(internalList, null);
    }

    @Override
    public E get(int i) {
        return internalList.get(i);
    }

    @Override
    public int size() {
        return internalList.size();
    }

}

Зауважте, що якщо ви не перекрили потрібні вам методи, то за замовчуванням реалізація AbstractListбуде кидати UnsupportedOperationExceptions.


12
+1. Імо, це більш конструктивно, ніж відповідь, яка проголосувала вище. Однак він має два незначні недоліки. Черга пріоритетів не підтримує випадковий доступ. Ви не можете заглянути (elementIndex). Так що ви не можете зробити, наприклад Integer maxVal = prioQueue.peek(prioQueue.size() - 1);. По-друге, якщо ви збираєтесь використовувати PriorityQueue просто як відсортований список, побачити PriorityQueueв коді буде менш інтуїтивно зрозуміло, ніж це було б SortedList, якби така структура даних існувала.
Alderath

12
І, подивившись на інше питання, яке хтось пов’язав у коментарях, ще одним великим недоліком є те, що ітератор PriorityQueue не гарантовано повертає елементи в будь-якому конкретному порядку. Отже, якщо я щось не помічаю, єдиний спосіб, наприклад, надрукувати всі об’єкти в черзі пріоритетів, щоб багаторазово опитувати () чергу, поки вона не порожня. Для мене це відчуває обмеженість кордону. Щоб надрукувати об'єкти в PriorityQueue двічі, спершу потрібно скопіювати PriorityQueue, а потім опитувати () всі об'єкти з оригінальної PriorityQueue, а потім pol () l усі об'єкти з копії.
Альдерат

1
Гм ... схоже, ти маєш рацію Олдерат. Ви не можете використовувати ітератор PriorityQueue для отримання елементів у очікуваному порядку. Схоже, я маю відредагувати свою відповідь.
Спікей

7
Черга з пріоритетом - це лише купа, ви можете отримати доступ лише до верху, тому що вона не належить до відповіді на питання.
bestsss

1
Також варто відзначити, що Collections.sort()навіть ви можете визначити функцію порівняння, яка використовується для сортування, за допомогою Comparatorоб’єкта.
Майк 'Помакс' Камерманс

71

Тому що поняття «Список» несумісне з концепцією автоматично відсортованої колекції. Суть Списку полягає в тому, що після дзвінка list.add(7, elem)дзвінок на list.get(7)повернеться elem. З автоматичним сортуванням списку елемент може опинитися у довільному положенні.


26

Оскільки всі списки вже "відсортовані" за порядком, додані елементи (замовлення FIFO), ви можете "вдатися" до них іншим порядком, включаючи природне упорядкування елементів, використовуючи java.util.Collections.sort().

Редагувати:

Списки як структури даних ґрунтуються на тому, що цікаво, це впорядкування, в яке вставлені елементи.

Набори не мають такої інформації.

Якщо ви хочете замовити, додавши час, використовуйте List. Якщо ви хочете замовити за іншими критеріями, використовуйте SortedSet.


22
Набір не дозволяє дублікати
bestsss

10
Я не думаю, що це дуже гарна відповідь. Звичайно, списки в API Java мають певне впорядкування, яке визначається тим, коли / як елементи були вставлені. Однак існування списків, упорядкованих у спосіб, який залежить від способу / часу вставки, не заважає мати інші структури даних, в яких порядок визначається іншим способом (наприклад, компаратором). В основному, ОП запитує, чому не існує структури даних, еквівалентної SortedSet, за винятком того, що структура даних повинна допускати кілька випадків елементів, рівних.
Альдерат

5
Тож моїм наступним запитанням було б: " Чому немає структури даних, яка працює як SortedSet, але яка може містити кілька рівних елементів? " (І, будь ласка, не відповідайте " тому що набори можуть містити лише один елемент ")
Alderath

@Alderath: Див stackoverflow.com/questions/416266/sorted-collection-in-java . Коротше кажучи: Використовуйте TreeMultiset Guava
Janus Troelsen

4
Списки НЕ "відсортовані", навіть якщо ви поставили "відсортовані" у подвійних лапках. Вони впорядковані.
Корай Тугай

20

Набір та карта - нелінійна структура даних. Список є лінійною структурою даних.

введіть тут опис зображення


Структура даних дерева SortedSetта SortedMapінтерфейси реалізуються TreeSetта TreeMapвідповідно використовуються використовуваний алгоритм реалізації Red-Black дерева . Таким чином, переконайтеся, що немає дублюваних елементів (або ключів у разі Map).

  • List вже підтримує впорядковану структуру даних на основі збору та індексу, дерева не є структурою даних на основі індексу.
  • Tree за визначенням не може містити дублікатів.
  • У Listнас може бути дублікати, тому немає TreeList(тобто немає SortedList).
  • Список підтримує елементи в порядку вставки. Отже, якщо ми хочемо сортувати список, який ми маємо використовувати java.util.Collections.sort(). Він сортує вказаний список у порядку зростання, відповідно до природного впорядкування його елементів.

14

JavaFX SortedList

Хоча це зайняло певний час, у Java 8 відсортовано List. http://docs.oracle.com/javase/8/javafx/api/javafx/collections/transformation/SortedList.html

Як ви бачите в javadocs, це частина колекцій JavaFX , призначена для надання впорядкованого перегляду на ObservableList.

Оновлення: Зауважте, що з Java 11 набір інструментів JavaFX перемістився поза JDK і тепер є окремою бібліотекою. JavaFX 11 доступний у форматі SDK для завантаження або з MavenCentral. Дивіться https://openjfx.io


2
На жаль, цей SortedList не працює як звичайний список - наприклад, він не має конструктора за замовчуванням (ви повинні сконструювати його за допомогою ObservableList, що б це не означало ...)
Ерел Сегал-Халеві

SortedList від JavaFX очевидно призначений для використання GUI-компонентів і через накладні НЕ підходить для просто списку відсортованих об'єктів. Також це означало б посилання на весь модуль FX, навіть якщо графічний інтерфейс не використовується в проекті.
COBRA.cH

1
@ COBRA.cH Так, це правда. Більш ефективний сортований список, можливо, може бути тонкою обгорткою навколо TreeMap, де ціле число використовується для підрахунку дублікатів ключа. Ви також можете використовувати TreeSet зі компаратором, який ніколи не повертає 0.
swpalmer

Чому голоси "за"? Питання починається з припущення про відсутність відсортованого списку в бібліотеках JDK. Ця відповідь виправляє це припущення. Подобається вам чи не подобається реалізація цього конкретного відсортованого списку - це не привід для голосування. Ця відповідь не рекомендує клас, просто вказує на його існування.
Василь Бурк

10

Для будь-яких новачків станом на квітень 2015 року Android тепер має бібліотеку сортування списку SortedList , розроблену спеціально для роботи RecyclerView. Ось повідомлення про це в блозі .


1
Варто зазначити, що під час цього коментаря Androids SortedList не підтримує функцію onItemMoved () з RecyclerView. Написати власний сортований список, який є менш ефективним - це те, що я повинен був зробити, щоб подолати обмеження.
Метью

4

Інший момент - складність часу операцій із вставкою. Для вставки списку очікується складність O (1). Але цього не можна було гарантувати відсортованим списком.

І найважливіший момент - списки не припускають нічого про їх елементи. Наприклад, ви можете зробити списки речей , які не реалізують equalsабо compare.


Ви можете мати список w / O (logn), вставити / видалити / знайти / містить, але не w / get (int).
bestsss

3
Останній пункт насправді не є гарним поясненням. Ви можете зробити SortedSetречі, які не реалізуються Comparable. Дивіться цей конструктор TreeSet .
Alderath

1
@Alderath - можливо, моє формулювання було занадто слабким. Однак зауваження стверджує, що елементи наборів і ключів дерев повинні бути порівнянні принаймні для рівності, тоді як елементи списку цього не потрібно. Незалежно від того, чи впорядкованість / рівність для наборів і дерев реалізована в порівнянні або в іншому місці, не має значення - але вам це потрібно.
Інго

Списки не гарантують O (1) вставлення , вони гарантують O (1) доступ . bigocheatsheet.com
Метт Квіглі

1
@Betlista маєш рацію! Я не можу оновити свій коментар, але Listінтерфейс Java не гарантує жодних специфікацій продуктивності його методів.
Метт Квіглі

3

Думайте про це так: Listінтерфейс має такі методи , як add(int index, E element), set(int index, E element). Контракт полягає в тому, що як тільки ви додасте елемент у позиції X, ви знайдете його там, якщо ви не додасте або вилучите елементи перед ним.

Якщо будь-яка реалізація списку зберігала б елементи в іншому порядку, ніж на основі індексу, вищезгадані методи списку не мали б сенсу.


2

Перший рядок у списку API говорить, що це впорядкована колекція (також відома як послідовність). Якщо ви сортуєте список, ви не можете підтримувати порядок, тому у Java немає TreeList.
Як каже API, список Java надихнувся від послідовності та перегляньте властивості послідовності http://en.wikipedia.org/wiki/Sequence_(mathematics )

Це не означає, що ви не можете сортувати список, але Java суворо до його визначення і не надає сортовані версії списків за замовчуванням.



2

Якщо ви шукаєте спосіб сортування елементів, але також зможете ефективно отримати доступ до них за індексом, ви можете зробити наступне:

  1. Використовуйте список випадкового доступу для зберігання (наприклад ArrayList)
  2. Переконайтесь, що він завжди відсортований

Потім додати або видалити елемент, який можна використовувати Collections.binarySearchдля отримання індексу вставки / видалення. Оскільки ваш список реалізує випадковий доступ, ви можете ефективно змінювати список за допомогою визначеного індексу.

Приклад:

/**
 * @deprecated
 *      Only for demonstration purposes. Implementation is incomplete and does not 
 *      handle invalid arguments.
 */
@Deprecated
public class SortingList<E extends Comparable<E>> {
    private ArrayList<E> delegate;

    public SortingList() {
        delegate = new ArrayList<>();
    }

    public void add(E e) {
        int insertionIndex = Collections.binarySearch(delegate, e);

        // < 0 if element is not in the list, see Collections.binarySearch
        if (insertionIndex < 0) {
            insertionIndex = -(insertionIndex + 1);
        }
        else {
            // Insertion index is index of existing element, to add new element 
            // behind it increase index
            insertionIndex++;
        }

        delegate.add(insertionIndex, e);
    }

    public void remove(E e) {
        int index = Collections.binarySearch(delegate, e);
        delegate.remove(index);
    }

    public E get(int index) {
        return delegate.get(index);
    }
}

1

Подумайте про використання індексованого дерева-карти . Це розширений TreeSet JDK, який забезпечує доступ до елемента за індексом та знаходження індексу елемента без ітерації або прихованих підлеглих списків, які створюють резервну копію дерева. Алгоритм заснований на оновленні ваги змінних вузлів щоразу, коли відбувається зміна.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.