0

I understand that Java does not possess a Sorted List for various conceptual reasons, but consider the case I need to have a collection which is kind of like a Priority Queue but also allows me random access (indexable), in other words, I need a List that follows a particular ordering. I would prefer not to use Collections.sort()

Preferable operation constraints:

retrieve - O(1) (index-based random access)
search - O(log n)
insert - O(log n)
delete - O(log n)

An iterator over the collection should give me all elements in Sorted Order (based on predefined Comparator supplied during instantiation of the data-structure)

I would prefer to use Java's inbuilt library to accomplish this, but feel free to suggest external libraries as well.

EDIT: TreeSet won't do as index based access is difficult, using wrapper collections is also not my best choice as removal would imply I need to remove from both collections.

EDIT2: I was unable to find an implementation and/or documentation for an indexable skip list this seems a bit relevant, can anyone help me find it ? Any comments for or against the data structure proposed is also welcome.

EDIT3: Though this may not be the most perfect answer, I want to add this piece of code that I wrote so that anyone who has similar problems for the need of a sorted list can use this if they find it useful.

Do check for errors (if any), and suggest improvements (especially to the sortedSubList method)

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;

public class SortedList<E> extends ArrayList<E> {
    private final Comparator<? super E> comparator;

    public SortedList(Comparator<? super E> comparator) {
        this.comparator = comparator;
    }

    public SortedList(int initialCapacity, Comparator<? super E> comparator) {
        super(initialCapacity);
        this.comparator = comparator;
    }

    @Override
    public boolean add(E e) {
        if (comparator == null)
            return super.add(e);
        if (e == null)
            throw new NullPointerException();
        int start = 0;
        int end = size() - 1;
        while (start <= end) {
            int mid = (start + end) / 2;
            if (comparator.compare(get(mid), e) == 0) {
                super.add(mid, e);
                return true;
            }
            if (comparator.compare(get(mid), e) < 0) {
                end = mid - 1;
            }
            else {
                start = mid + 1;
            }
        }
        super.add(start, e);
        return true;
    }

    @Override
    public boolean contains(Object o) {
        if (comparator == null)
            return super.contains(o);
        if (o == null)
            return false;
        E other = (E) o;
        int start = 0;
        int end = size() - 1;
        while (start <= end) {
            int mid = (start + end) / 2;
            if (comparator.compare(get(mid), other) == 0) {
                return true;
            }
            if (comparator.compare(get(mid), other) < 0) {
                end = mid - 1;
            }
            else {
                start = mid + 1;
            }
        }
        return false;
    }

    @Override
    public int indexOf(Object o) {
        if (comparator == null)
            return super.indexOf(o);
        if (o == null)
            throw new NullPointerException();
        E other = (E) o;
        int start = 0;
        int end = size() - 1;
        while (start <= end) {
            int mid = (start + end) / 2;
            if (comparator.compare(get(mid), other) == 0) {
                return mid;
            }
            if (comparator.compare(get(mid), other) < 0) {
                end = mid - 1;
            }
            else {
                start = mid + 1;
            }
        }
        return -(start+1);
    }

    @Override
    public void add(int index, E e) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        throw new UnsupportedOperationException();
    }

    @Override
    public E set(int index, E e) {
        throw new UnsupportedOperationException();
    }

    public SortedList<E> sortedSubList(int fromIndex, int toIndex) {
        SortedList<E> sl = new SortedList<>(comparator);
        for (int i = fromIndex; i < toIndex; i++)
            sl.add(get(i));
        return sl;
    }
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Codi
  • 511
  • 3
  • 19
  • `TreeSet` is what you want – Uma Kanth Oct 24 '15 at 08:54
  • You should clarify that "allows me random access" means you want it to be indexable, assuming that's what you mean. – CupawnTae Oct 24 '15 at 08:55
  • 1
    It's going to be hard to get O(1) indexing and O(log n) insertion/deletion at the same time. O(1) indexing would require some sort of array-like storage, while O(log n) insertion and deletion mean you can't afford to shift half the elements every time you update the data structure. – user2357112 Oct 24 '15 at 08:56
  • 1
    similar question http://stackoverflow.com/questions/4249088/is-there-an-indexable-sorted-list-in-the-java-util-package – CupawnTae Oct 24 '15 at 08:58
  • ok, say we relax some of the constraints a bit, what is the closest thing we can get ? say O(n) for insert. – Codi Oct 24 '15 at 09:00
  • Possible duplicate of [Why is there no SortedList in Java?](http://stackoverflow.com/questions/8725387/why-is-there-no-sortedlist-in-java) – ctomek Oct 24 '15 at 09:04
  • 1
    @ctomek: Not a dupe. This question has specific, unusual requirements not featured in the other question and not fulfilled by its answers. – user2357112 Oct 24 '15 at 09:11
  • @CupawnTae The question at http://stackoverflow.com/questions/4249088/is-there-an-indexable-sorted-list-in-the-java-util-package seems a bit likely to solve my case, is there a library which implements this data structure (indexable skip list), and its documentation so that I can analyze it ? – Codi Oct 24 '15 at 09:14
  • How do you know you have these complexity requirements? Have you profiled your application and seen that it's too slow with existing collections? – Sean Patrick Floyd Oct 24 '15 at 09:14
  • @SeanPatrickFloyd To a certain extent, yes, though according to the 3rd comment by user2357112 , it seems that this is impossible, what is the next best thing ? – Codi Oct 24 '15 at 09:19
  • @Codi sorry, I don't know - just found that searching for `indexable treeset` – CupawnTae Oct 24 '15 at 09:56

3 Answers3

1

It's hard to get O(1) indexing and O(log n) insertion/deletion in the same data structure. O(1) indexing means we can't afford the link-following involved in indexing a tree, list, skip list, or other link-based data structure, while O(log n) modification means we can't afford to shift half the elements of an array on every insertion. I don't know if it's possible to fulfill these requirements simultaneously.

If we relax one of these requirements, things become much easier. For example, O(log n) for all operations can be achieved by an indexable skip list or a self-balancing BST with nodes that keep track of the size of the subtree rooted at the node. Neither of these can be built on top of the skip list or BST in Java's standard library, though, so you'd probably need to install another library or write your own data structure.

O(1) indexing, O(log n) search, and O(n) insert and delete can be done by keeping a sorted ArrayList and using Collections.binarySearch to search for elements or insert/delete positions. You never need to call Collections.sort, but you still need to call the ArrayList's O(n) insert and delete methods. This is probably the easiest option to build on top of Java's built-in tools. Note that with recent Java versions, Collections.sort is an adaptive mergesort that would take O(n) time to sort an array where only the last element is out of sorted order, so you could probably get away with relying on Collections.sort. However, that's an implementation detail that alternative Java implementations don't have to follow.

user2357112
  • 260,549
  • 28
  • 431
  • 505
1

If your primary goal is O(1) for indexed lookup (get()), then you can implement your own class implementing List, backed by an array, using Arrays.binarySearch().

retrieve: get(int)         - O(1)     - array index
search:   contains(Object) - O(log n) - binarySearch
          indexOf(Object)  - O(log n) - binarySearch
insert:   add(E)           - O(n)     - binarySearch + array shift
delete:   remove(int)      - O(n)     - array shift
          remove(Object)   - O(n)     - binarySearch + array shift

The add(E) method is violating the List definition (append), but is consistent with the Collection definition.

The following methods should throw UnsupportedOperationException:

add(int index, E element)
addAll(int index, Collection<? extends E> c)
set(int index, E element)

If duplicate values are not allowed, which could be a logical restriction, consider also implementing NavigableSet, which is a SortedSet.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • "implementing `List`" - this doesn't seem particularly useful. Most code that expects a List will break if handed this data structure. `Collection` should be implemented, but despite implementing `get(int)`, `indexOf(Object)`, and `remove(int)`, this class probably shouldn't implement `List`. – user2357112 Oct 24 '15 at 18:53
  • @user2357112 Why would "most code" break? What code is that? Please note that, except for the `add(E)` method, all other methods can be implemented 100% to spec. – Andreas Oct 24 '15 at 19:15
0

Build a custom collection that is backed by an ArrayList and a TreeSet. Delegate the random access to the ArrayList and the search to the TreeSet. Of course this means that every write operation will be very expensive, as it will have to sort the ArrayList every time. But the reads should be very efficient.

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588