Будь-яка реалізація впорядкованого набору в Java?


98

Якщо хто -то знайомий з Objective-C є колекція під назвою , NSOrderedSetякий діє як Set і його елементи можуть бути доступні як масив «ов з них.

Чи є щось подібне на Java?

Я чув, що існує така колекція LinkedHashMap, але я не знайшов нічого подібного для набору.


Я працюю над подібною проблемою в C ++. за допомогою NSOrderedSet, чи можемо ми отримати доступ до елементів у тому порядку, який ми вставили в нього?
Віней

Чи знаєте ви, як отримати вище функціональності в C ++? i, e діє як SET та може бути доступний як елементи масиву?
Віней,

Відповіді:


118

Погляньте на клас LinkedHashSet

З документа Java :

Реалізація хеш-таблиці та пов'язаного списку інтерфейсу Set із передбачуваним порядком ітерацій . Ця реалізація відрізняється від HashSet тим, що вона підтримує подвійно пов'язаний список, що проходить через усі його записи. Цей зв’язаний список визначає порядок ітерацій, тобто порядок вставки елементів у набір (порядок вставки) . Зверніть увагу, що порядок вставки не впливає, якщо елемент повторно вставляється в набір . (Елемент e повторно вставляється в набір s, якщо s.add (e) викликається, коли s.contains (e) повертає true безпосередньо перед викликом.).


Велике спасибі. Здається, це тривіально, LinkedHashMapале я якось не знайшов.
Уко


9
Чому ця відповідь отримує стільки голосів? Це взагалі не відповідь на запитання. У ній немає функції, LinkedHashSetяка дозволяє з’ясувати, в якому індексі також знаходиться елемент.
searchengine27,

31

Кожен набір має ітератор (). Звичайний ітератор HashSet є досить випадковим, TreeSet робить це за порядком сортування, LinkedHashSet ітератор ітерації за порядком вставки.

Однак ви не можете замінити елемент у LinkedHashSet. Ви можете видалити одне та додати інше, але новий елемент не буде на місці оригіналу. У LinkedHashMap ви можете замінити значення існуючого ключа, і тоді значення все ще будуть у вихідному порядку.

Крім того, ви не можете вставити в певному положенні.

Можливо, вам краще скористатися ArrayList з явною перевіркою, щоб уникнути вставлення дублікатів.


Я хочу мати можливість встановити / отримати елемент у певній позиції та отримати їх за замовленням, я їх додав. Це шви, які LinkedHashSetповинні це зробити. Дякую за відповідь
Уко

11

Погляньте на стандартний документ Java щодо API . Прямо поруч LinkedHashMapє LinkedHashSet. Але зверніть увагу, що порядок у них - це порядок вставки, а не природний порядок елементів. І ви можете робити ітерації лише в такому порядку, а не виконувати довільний доступ (за винятком підрахунку кроків ітерації).

Існує також інтерфейс, SortedSetреалізований TreeSetі ConcurrentSkipListSet. Обидва дозволяють ітерацію в природному порядку їх елементів або a Comparator, але не довільний доступ чи порядок вставки.

Для структури даних, яка має як ефективний доступ за допомогою індексу, так і може ефективно реалізовувати заданий критерій, вам знадобиться пропустити список , але реалізації з цією функцією в Java Standard API немає, хоча я впевнений, що знайти його легко в інтернеті.


Можливо, я неправильно розумію ваш коментар, але у мене склалося враження, що з Java 1.6 існувало кілька колекцій за замовчуванням на основі списків пропусків (як, скажімо, ConcurrentSkipListSet тощо).
TacticalCoder

@ user988052: так, але ті не реалізують довільний доступ за допомогою індексу (хоча моє розуміння списків пропуску говорить, що це повинно бути можливим), що, мабуть, хоче Уко.
Michael Borgwardt

@MichaelBorgwardt Java 6 і пізніших версій включає пару реалізацій пропуску списку: ConcurrentSkipListMapі ConcurrentSkipListSet. Обидва підтримують сортування на основі природного порядку або компаратора. Я не розумію, чи надають вони випадковий доступ або порядок входу, які ви обговорюєте.
Василь Бурк

@BasilBourque: гарна знахідка та спасибі за редагування. OP хотів отримати доступ за індексом, і тепер, коли я його подивився і задумався, я думаю, що списки пропуску насправді теж не мають такої можливості ...
Майкл Борґвардт

5

Це правильна відповідь. В відміну від LHSet, TreeSet робить реалізацію java.util.SortedSet.
vemv

40
упорядковані та відсортовані - це різні речі. TreeSet відсортовано, не впорядковано
andrii

2
Точно, впорядкований відноситься до порядку вставки (як працює Список), тоді як відсортований - до фактичного впорядкування елементів на основі деяких критеріїв.
Корнел Массон

5

Спробуйте скористатися java.util.TreeSetцим знаряддям SortedSet.

Щоб процитувати документ:

"Елементи впорядковуються за їх природним упорядкуванням або за допомогою компаратора, який надається під час встановленого часу, залежно від того, який конструктор використовується"

Зверніть увагу, що додавання, видалення та вміст має журнал часових витрат (n).

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

YourType[] array = someSet.toArray(new YourType[yourSet.size()]); 

Цей масив буде відсортовано за тими ж критеріями, що і TreeSet (природний або за допомогою компаратора), і в багатьох випадках це матиме перевагу замість Arrays.sort ()


1
Мені потрібне впорядкування, як у ArrayList ei, якщо я покладу спочатку елемент, cа потім елемент a, коли я перебираю колекцію, я хочу отримати їх у тому ж порядку: cі aт. Д.
Uko

1

treeset - це впорядкований набір, але ви не можете отримати доступ через індекс елементів, просто перегляньте його або перейдіть до початку / кінця.


За допомогою TreeSet ви збільшите собівартість. LinkedHashSet має нижчу вартість.
Карлос

0

Якщо ми говоримо про недорогу реалізацію пропущеного списку, мені цікаво з точки зору великого O, яка вартість цієї операції:

Масив YourType [] = someSet.toArray (новий YourType [yourSet.size ()]);

Я маю на увазі, що це завжди застряє у цілому створеному масиві, тому це O (n):

java.util.Arrays#copyOf

1
Це залежить від експлуатаційних характеристик ітератора та size()методу базового набору. Ітерація зазвичай O(n), розмір зазвичай, O(1)за винятком того, ConcurrentSkipListSetде він є O(n).
Ян Робертс,


0

Ви також можете отримати якусь утиліту з двонаправленої карти, наприклад, BiMapвід Google Guava

За допомогою a BiMapви можете досить ефективно зіставити ціле число (для випадкового доступу до індексу) до будь-якого іншого типу об’єкта. BiMaps - це "один на один", тому будь-яке ціле число має, щонайбільше, один елемент, пов'язаний з ним, а будь-який елемент має одне ціле число. Він розумно підкріплений двома HashTableекземплярами, тому використовує майже подвійну пам’ять, але набагато ефективніший, ніж звичай List, що стосується обробки, оскільки contains()(що викликається, коли елемент додається, щоб перевірити, чи він вже існує) - це постійний час і паралельно зручна робота, як-от HashSet, а Listреалізація набагато повільніша.


0

У мене була схожа проблема. Мені не зовсім потрібен був замовлений набір, а більше список зі швидким indexOf/ contains. Оскільки я там нічого не знайшов, я сам це реалізував. Ось код, він реалізує і те, Setі List, хоча не всі операції з масовим переліком є ​​такими швидкими, як ArrayListверсії.

застереження: не перевірено

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import java.util.Collection;
import java.util.Comparator;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import static java.util.Objects.requireNonNull;

/**
 * An ArrayList that keeps an index of its content so that contains()/indexOf() are fast. Duplicate entries are
 * ignored as most other java Set's do.
 */
public class IndexedArraySet<E> extends ArrayList<E> implements Set<E> {

    public IndexedArraySet() { super(); }

    public IndexedArraySet(Iterable<E> c) {
        super();
        addAll(c);
    }

    private HashMap<E, Integer> indexMap = new HashMap<>();

    private void reindex() {
        indexMap.clear();
        int idx = 0;
        for (E item: this) {
            addToIndex(item, idx++);
        }
    }

    private E addToIndex(E e, int idx) {
        indexMap.putIfAbsent(requireNonNull(e), idx);
        return e;
    }

    @Override
    public boolean add(E e) {
        if(indexMap.putIfAbsent(requireNonNull(e), size()) != null) return false;
        super.add(e);
        return true;
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        return addAll((Iterable<? extends E>) c);
    }
    public boolean addAll(Iterable<? extends E> c) {
        boolean rv = false;
        for (E item: c) {
            rv |= add(item);
        }
        return rv;
    }

    @Override
    public boolean contains(Object e) {
        return indexMap.containsKey(e);
    }

    @Override

    public int indexOf(Object e) {
        if (e == null) return -1;
        Integer i = indexMap.get(e);
        return (i == null) ? -1 : i;
    }

    @Override
    public int lastIndexOf(Object e) {
        return indexOf(e);
    }

    @Override @SuppressWarnings("unchecked")
    public Object clone() {
        IndexedArraySet clone = (IndexedArraySet) super.clone();
        clone.indexMap = (HashMap) indexMap.clone();
        return clone;
    }

    @Override
    public void add(int idx, E e) {
        if(indexMap.putIfAbsent(requireNonNull(e), -1) != null) return;
        super.add(idx, e);
        reindex();
    }

    @Override
    public boolean remove(Object e) {
        boolean rv;
        try { rv = super.remove(e); }
        finally { reindex(); }
        return rv;
    }

    @Override
    public void clear() {
        super.clear();
        indexMap.clear();
    }

    @Override
    public boolean addAll(int idx, Collection<? extends E> c) {
        boolean rv;
        try {
            for(E item : c) {
                // check uniqueness
                addToIndex(item, -1);
            }
            rv = super.addAll(idx, c);
        } finally {
            reindex();
        }
        return rv;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        boolean rv;
        try { rv = super.removeAll(c); }
        finally { reindex(); }
        return rv;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        boolean rv;
        try { rv = super.retainAll(c); }
        finally { reindex(); }
        return rv;
    }

    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        boolean rv;
        try { rv = super.removeIf(filter); }
        finally { reindex(); }
        return rv;
    }

    @Override
    public void replaceAll(final UnaryOperator<E> operator) {
        indexMap.clear();
        try {
            int duplicates = 0;
            for (int i = 0; i < size(); i++) {
                E newval = requireNonNull(operator.apply(this.get(i)));
                if(indexMap.putIfAbsent(newval, i-duplicates) == null) {
                    super.set(i-duplicates, newval);
                } else {
                    duplicates++;
                }
            }
            removeRange(size()-duplicates, size());
        } catch (Exception ex) {
            // If there's an exception the indexMap will be inconsistent
            reindex();
            throw ex;
        }

    }

    @Override
    public void sort(Comparator<? super E> c) {
        try { super.sort(c); }
        finally { reindex(); }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.