Подтвердить что ты не робот

Коллекция, которая использует ListIterator и является уникальным списком

Это недавно раздражало меня в проекте, и мой Google phoo не может найти подходящий ответ.

Есть ли коллекция, которая имеет доступ к ListIterator, но допускает только уникальные значения внутри коллекции?

Исходя из этого, у меня есть коллекция предметов, в которой в этой коллекции должен быть только один элемент. Я также хочу иметь возможность просматривать эту коллекцию в обоих направлениях, а также сортировать ее или разрешить сортировать ее с помощью Collections.Sort();

Я не нашел ничего подходящего и должен был написать свой собственный класс, используя следующий код:

public class UniqueArrayList<E> extends ArrayList<E> {
    @Override
    public boolean add(E element){
        if (this.contains(element))
            return false;
        else
            return super.add(element);
    }

    @Override
    public void add(int index, E element){
        if (this.contains(element))
            return;
        else
            super.add(index, element);
    }

    @Override
    public boolean addAll(Collection<? extends E> c){
        if (new HashSet<E>(c).size() < c.size())
            return false;
        for(E element : c){
            if (this.contains(c))
                return false;
        }
        return super.addAll(c);
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        if (new HashSet<E>(c).size() < c.size())
            return false;
        for(E element : c){
            if (this.contains(c))
                return false;
        }
        return super.addAll(index, c);
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > this.size())
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }

    @Override
    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

    @Override
    public Iterator<E> iterator() {
        return new Itr();
    }

    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size();
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size())
                throw new NoSuchElementException();
            Object[] elementData = UniqueArrayList.this.toArray();
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                UniqueArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }


    }

    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = UniqueArrayList.this.toArray();
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
                //Need to allow this for the collections sort to work!
                //if (!UniqueArrayList.this.contains(e))
                UniqueArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                UniqueArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }
}

Однако это далеко не идеально, поскольку я не могу переопределить ListIterator.set();, потому что Collections.sort(); использует его для перемещения элементов в списке. Если я попытаюсь предотвратить добавление в список неуникальных элементов, сортировка никогда не произойдет.

Итак, есть ли у кого-нибудь лучший метод или знать о другой коллекции, которая соблюдает правила, которые я хотел бы? Или мне просто нужно жить с этой довольно раздражающей проблемой?

[Править]

Это метод Collections.sort();:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    Object[] a = list.toArray();
    Arrays.sort(a);
    ListIterator<T> i = list.listIterator();
    for (int j=0; j<a.length; j++) {
        i.next();
        i.set((T)a[j]);
    }
}

Причины, по которым они приводят это:

Эта реализация сбрасывает указанный список в массив, сортирует массив и перебирает список, сбрасывая каждый элемент из соответствующая позиция в массиве. Это позволяет избежать 2 log (n) производительность, которая может возникнуть в результате сортировки связанных список на месте.

4b9b3361

Ответ 1

Как сказал Thor Stan, TreeSet доставит вам больше всего того, чего вы хотите. Он гарантирует уникальность элементов, он держит их отсортированными, и вы можете перебирать их в любом направлении с помощью iterator() или descendingIterator().

Не совсем понятно, почему вы просите ListIterator. Большинство вещей о ListIterator очень позиционные: понятие индекса или добавление чего-то в текущую позицию или установка текущего элемента. Это не имеет смысла для сортированного набора.

Один аспект ListIterator, который вы, возможно, ищете, - это способность обратного направления в середине итерации. Вы не можете сделать это напрямую с помощью итератора TreeSet, поскольку он предлагает доступ только через обычный Iterator вместо ListIterator.

Однако, TreeSet реализует интерфейс NavigableSet, который позволяет вам последовательно перемещать элементы в любом направлении. Интерфейс NavigableSet является вспомогательным интерфейсом SortedSet, который предоставляет методы first() и last(), чтобы вы начали с одного из "концов" набора. Когда у вас есть элемент в наборе, вы можете шагнуть в любом направлении, используя методы lower(E) и higher(E). Или, если вы хотите начать где-то посередине, вы не можете начать с позиции по индексу, но вы можете начать со значения (которое не должно быть членом набора), а затем вызвать lower(E) или higher(E).

Например:

    TreeSet<String> set = new TreeSet<>(
        Arrays.asList("a", "z", "b", "y"));
    String cur;

    cur = set.first();     // a
    cur = set.higher(cur); // b
    cur = set.higher(cur); // y
    cur = set.higher(cur); // z
    cur = set.lower(cur);  // y
    cur = set.lower(cur);  // b
    cur = set.lower(cur);  // a
    cur = set.lower(cur);  // null

Ответ 2

Когда вам нужны уникальные значения, вы должны попытаться переключиться на Set. Вы можете использовать TreeSet вместе со экземпляром Comparator для сортировки записей. Метод descendingSet() TreeSet даст вам обратный порядок. Если вам действительно нужен ListIterator, в какой-то момент вы можете создать временный список из набора.

Ответ 3

Java считает, что List позволяет нестандартным элементам и Set не указывать. Наборы явно не поддерживают ListIterators, и поэтому код, который использует ListIterator, может предположить, что базовая коллекция не является Set.

Java 8 больше не использует ListIterator для сортировки, но если вы застряли в Java 7, который вам действительно не поможет. В основном зависит от вашего контекста и использования, может быть полезно использовать либо Set, как сказал Тор, в своем ответе, создав List по требованию, когда это необходимо.

Другой вариант - просто предоставить свой собственный ListIterator, который обращается к списку с помощью метода, который не проверяет наличие дубликатов. Преимущество этого заключалось бы в том, чтобы не создавать посторонние объекты, но опция Set скорее всего приведет к сокращению кода и вряд ли будет значительной по производительности.

Возможно, есть и другие варианты, но я не могу придумать никаких изящных.

Ответ 4

Это проблема без простых ответов.

Проблема в том, что вы нарушили контракт для add(int, E). Этот метод должен либо добавить элемент, либо вызвать исключение - ему не разрешено возвращать без добавления элемента.

Если вы переопределите set(int, E), чтобы иногда он не устанавливал элемент, он также нарушил бы контракт для этого метода. это предотвратило бы работу Collections.sort() от того, как вы идентифицировали.

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

Другие испытывали эти трудности - например, см. java docs для SetUniqueList.

Другая проблема с вашей реализацией заключается в том, что она будет чрезвычайно медленной, потому что ArrayList.contains() - это линейный поиск.

Одним из решений было бы написать класс, который использует как ArrayList, так и HashSet, и написать собственные версии add и set, а не разорвать контракты для обычных версий. Это не проверено, но вы получаете эту идею.

public final class MyList<E extends Comparable<? super E>> extends AbstractList<E> {

    private final List<E> list = new ArrayList<>();
    private final Set<E> set = new HashSet<>();

    public E get(int i) {
        return list.get(i);
    }

    public int size() {
        return list.size();
    } 

    public boolean tryAdd(E e) {
        return set.add(e) && list.add(e);
    }

    public boolean tryAdd(int i, E e) {
        if (set.add(e)) {
            list.add(i, e);
            return true;
        }
        return false;
    }

    public boolean trySet(int i, E e) {
        return set.add(e) && set.remove(list.set(i, e));
    }

    public boolean remove(Object o) {
        return set.remove(o) && list.remove(o);
    }

    public void sort() {
        Collections.sort(list);
    }

    // One bonus of this approach is that contains() is now O(1)
    public boolean contains(Object o) {
        return set.contains(o);
    }

    // rest omitted.
}

Что-то вроде этого не нарушит контракт для List. Обратите внимание, что в этой версии add и set введите UnsupportedOperationException, потому что это поведение, унаследованное от AbstractList. Вы можете sort вызвать myList.sort(). Кроме того, метод listIterator будет работать, но вы не сможете использовать его для set или add (хотя remove будет работать).

Если вам нужны элементы add или set, итерации по этому List, вам нужно будет использовать явный индекс, а не listIterator. Лично я не считаю это серьезной проблемой. listIterator необходим для списков типа LinkedList, у которых нет метода времени get, но для ArrayList он приятный, но не существенный.