Любая реализация упорядоченного набора на Java?


99

Если кто-нибудь знаком с Objective-C, существует коллекция, NSOrderedSetкоторая действует как Set, и к ее элементам можно обращаться как к элементам Array .

Есть ли что-нибудь подобное в Java?

Я слышал, что есть такая коллекция LinkedHashMap, но я не нашел ничего подобного для набора.


Я работаю над аналогичной проблемой на С ++. с помощью NSOrderedSet можем ли мы получить доступ к элементам в том порядке, в котором они были вставлены?
Vinay

Вы знаете, как получить вышеуказанные функции в C ++? i, e действует как SET и может быть доступен как элементы массива?
Vinay

Ответы:


120

Взгляните на класс LinkedHashSet

Из документа Java :

Реализация хэш-таблицы и связанного списка интерфейса Set с предсказуемым порядком итераций . Эта реализация отличается от HashSet тем, что поддерживает двусвязный список, проходящий через все его записи. Этот связанный список определяет порядок итераций, то есть порядок, в котором элементы были вставлены в набор (порядок вставки) . Обратите внимание, что порядок вставки не изменяется, если элемент повторно вставляется в набор . (Элемент e повторно вставляется в набор s, если s.add (e) вызывается, когда s.contains (e) вернет true непосредственно перед вызовом.).


Большое спасибо. Кажется, это банально, LinkedHashMapно я почему-то не нашел.
Uko


9
Почему этот ответ получил столько голосов? Это вообще не ответ на вопрос. В нем нет функции LinkedHashSet, позволяющей определить, в каком индексе находится элемент.
searchchengine27 05

32

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

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

Кроме того, вы не можете вставить в определенную позицию.

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


Я хочу иметь возможность устанавливать / получать элемент в определенной позиции и получать их по порядку, в котором я их добавил. Похоже, что это LinkedHashSetдолжно сработать. Спасибо за ответ
Uko

11

Взгляните на стандартную документацию API Java . Рядом LinkedHashMapнаходится значок LinkedHashSet. Но обратите внимание, что порядок в них - это порядок вставки, а не естественный порядок элементов. И вы можете выполнять итерацию только в этом порядке, а не выполнять произвольный доступ (кроме подсчета шагов итерации).

Также существует интерфейс, SortedSetреализованный TreeSetи ConcurrentSkipListSet. Оба допускают итерацию в естественном порядке их элементов или а Comparator, но не в произвольном порядке доступа или в порядке вставки.

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


Возможно, я неправильно понимаю ваш комментарий, но у меня создалось впечатление, что с Java 1.6 было несколько коллекций по умолчанию, основанных на списках пропуска (например, ConcurrentSkipListSet и т. Д.).
TacticalCoder

@ user988052: да, но они не реализуют произвольный доступ по индексу (хотя я понимаю, что списки пропусков говорят, что это должно быть возможно), что, кажется, то, что хочет Uko.
Майкл Боргвардт

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

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

5

Это правильный ответ. В отличие от LHSet, TreeSet делает реализацию java.util.SortedSet.
vemv

40
упорядоченные и отсортированные - это разные вещи. TreeSet отсортирован, а не упорядочен
Андрей

2
Точно, упорядоченный относится к порядку вставки (способу работы списка), а отсортированный относится к упорядочиванию элементов постфактум на основе некоторых критериев.
Корнел Массон

5

Попробуйте использовать java.util.TreeSetэти орудия SortedSet.

Процитирую документ:

«Элементы упорядочиваются с использованием их естественного порядка или с помощью компаратора, предоставляемого во время создания набора, в зависимости от того, какой конструктор используется»

Обратите внимание, что add, remove и contains имеют журнал затрат времени (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

Если мы говорим о недорогой реализации skip-list, мне интересно, в терминах большого O, какова стоимость этой операции:

YourType [] array = 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()(который вызывается, когда элемент добавляется, чтобы проверить, существует ли он уже), является постоянным временем и параллельная операция, такая как HashSets, в то время как 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.