Java에서 정렬 된 배열 목록


85

이에 대한 빠른 답변을 찾을 수 없어서 당황 스럽습니다. 기본적으로 java.util.List인터페이스 를 구현 하지만 구성원을 정렬 된 순서로 저장 하는 Java의 데이터 구조를 찾고 있습니다. 노멀 ArrayList을 사용 Collections.sort()하여 사용할 수 있다는 것을 알고 있지만 가끔 내 목록에서 회원을 추가하고 자주 검색하는 시나리오가 있습니다. 새로운 것이 추가되었습니다. 누구든지 JDK 또는 타사 라이브러리에 존재하는 그런 것을 가리킬 수 있습니까?

편집 : 데이터 구조는 중복을 보존해야합니다.

답변 요약 :이 모든 것이 매우 흥미롭고 많은 것을 배웠습니다. 특히 Aioobe는 위의 요구 사항 (주로 중복을 지원하는 정렬 된 java.util.List 구현)을 달성하려는 노력에 대해 언급 할 가치가 있습니다. 나는 그의 대답을 내가 요청한 것에 대해 가장 정확한 것으로 받아 들였고, 내가 요청한 것이 정확히 내가 필요로하는 것이 아니더라도 내가 찾고있는 것의 의미를 자극한다고 생각했다.

내가 요청한 문제는 List 인터페이스 자체와 인터페이스의 선택적 메서드 개념에 있습니다. javadoc을 인용하려면 :

이 인터페이스의 사용자는 목록에서 각 요소가 삽입되는 위치를 정확하게 제어 할 수 있습니다.

정렬 된 목록에 삽입하면 삽입 지점을 정확하게 제어 할 수 없습니다. 그런 다음 몇 가지 방법을 어떻게 처리할지 생각해야합니다. 가지고 add예를 들면 :

public boolean add (Object o)

 Appends the specified element to the end of this list (optional operation).

이제 계약을 파기하고 추가 2의 정렬 된 버전을 구현) 분들께) 중 하나의 불편한 상황에서 남아있는 add당신의 정렬 된 순서 3) 떠나 파괴, 목록의 마지막에 요소를 추가 add던지는하여 선택적으로 (아웃) UnsupportedOperationException및 정렬 된 순서로 항목을 추가하는 다른 방법을 구현.

옵션 3이 아마도 최고 일 것입니다.하지만 사용할 수없는 add 메서드와 인터페이스에없는 다른 sortedAdd 메서드가있는 것은 좋지 않습니다.

기타 관련 솔루션 (특정 순서 없음) :

  • 내가 요청한 것보다 필요한 것에 가장 가까운 java.util.PriorityQueue . 큐는 제 경우에 개체 컬렉션의 가장 정확한 정의는 아니지만 기능적으로 필요한 모든 것을 수행합니다.
  • net.sourceforge.nite.util.SortedList . 그러나이 구현은 add(Object obj)메서드 에서 정렬을 구현하여 List 인터페이스의 계약을 깨뜨리고 add(int index, Object obj). 일반적인 합의에 따르면 throw new UnsupportedOperationException()이 시나리오에서는 더 나은 선택이 될 수 있습니다.
  • Guava의 TreeMultiSet 중복을 지원하는 집합 구현
  • ca.odell.glazedlists.SortedList 이 클래스는 javadoc에주의 사항이 있습니다.Warning: This class breaks the contract required by List

4
가끔 삽입하고 자주 읽는다면 삽입하는 동안 그냥 정렬하지 않는 이유는 무엇입니까?
serg

답변:


62

최소한의 솔루션

여기에 "최소한"해결책이 있습니다.

class SortedArrayList<T> extends ArrayList<T> {

    @SuppressWarnings("unchecked")
    public void insertSorted(T value) {
        add(value);
        Comparable<T> cmp = (Comparable<T>) value;
        for (int i = size()-1; i > 0 && cmp.compareTo(get(i-1)) < 0; i--)
            Collections.swap(this, i, i-1);
    }
}

삽입은 선형 시간으로 실행되지만 어쨌든 ArrayList를 사용하여 얻을 수있는 것입니다 (삽입 된 요소의 오른쪽에있는 모든 요소는 한 방향 또는 다른 방향으로 이동해야 함).

비교할 수없는 것을 삽입하면 ClassCastException이 발생합니다. (이것은 PriorityQueue또한 취한 접근 방식입니다 : 자연 순서에 의존하는 우선 순위 큐는 또한 비교할 수없는 객체의 삽입을 허용하지 않습니다 (그렇게하면 ClassCastException이 발생할 수 있습니다). )

재정의 List.add

정렬 된 방식으로 요소를 삽입 하도록 재정의 List.add(또는 List.addAll그 문제에 대해)하는 것은 인터페이스 사양을 직접 위반 하는 것 입니다. 할 수있는 일은이 메서드를 재정 의하여 UnsupportedOperationException.

문서에서 List.add:

boolean add(E e)
    이 목록의 끝에 지정된 요소를 추가합니다 (선택적 작업).

add두 버전, addAll및의 두 버전 모두에 동일한 추론이 적용됩니다 set. (모두 목록 인터페이스에 따른 선택적 작업입니다.)


일부 테스트

SortedArrayList<String> test = new SortedArrayList<String>();

test.insertSorted("ddd");    System.out.println(test);
test.insertSorted("aaa");    System.out.println(test);
test.insertSorted("ccc");    System.out.println(test);
test.insertSorted("bbb");    System.out.println(test);
test.insertSorted("eee");    System.out.println(test);

....인쇄물:

[ddd]
[aaa, ddd]
[aaa, ccc, ddd]
[aaa, bbb, ccc, ddd]
[aaa, bbb, ccc, ddd, eee]

좋은 시작이지만 add 또는 addall을 호출하면 정렬되지 않은 방식으로 구성원이 추가됩니다.
Chris Knight

예. 목록에 추가하는 것 외에는 List 인터페이스를 직접 위반하는 것입니다. 업데이트 된 답변을 참조하십시오.
aioobe

@aioobe 좋은 지적입니다. 그러나 인터페이스 메서드의 지원되지 않는 작업은 코드 냄새가 아닙니까? 올바른 방법은 ArrayList를 확장하지 않고 List를 구현하는 것이지만 List가이 목적을위한 것이 아닐 수도 있습니다. 목록에 대한 Javadoc에서 : The user of this interface has precise control over where in the list each element is inserted정렬 된 방식으로 요소를 삽입하는 가장 좋은 설명이 아니며 여전히 add(int index, Object obj)인터페이스 메소드 를 처리해야합니다 . 이러한 문제는 List가 정렬 된 방식으로 구현되지 않은 이유를 설명 할 수 있습니다.
Chris Knight

글쎄, 그 수술은 이유 때문에 선택 사항입니다. .addSortedArrayList에서 수행 할 때 UnsupportedExceptionOperation이 발생하더라도 놀라지 않을 것 입니다. 예, 두 버전의 add, 두 버전의 addAll 및 set 모두에 동일한 추론이 적용됩니다. (모두 목록 인터페이스에 따른 선택적 작업입니다.)
aioobe

아, 나는 그들이 선택적 작업이라는 것을 몰랐습니다. 줄거리가 두꺼워집니다 ...;)
Chris Knight

10

7
즉, 임의 액세스가 없습니다.
Thilo

1
큐 기반 우선 순위 힙이 목록을 구현하지 않습니다.
zengr

3
물론 정렬 순서를 유지하는 목록을 사용하면 인덱스가 항상 변경되므로 어쨌든 임의 액세스가 필요하지 않을 수 있습니다.
Thilo

5
@Qwerky, 정확한 답변이 항상 최선의 답변이 아니거나 OP가 실제로 추구하는 답변이 아닙니다.
aioobe

3
우선 순위 대기열은 반복에 대해 정렬 된 순서를 부여하지 않습니다.
marcorossi

6

SortedList 살펴보기

이 클래스는 정렬 된 목록을 구현합니다. 두 개체를 비교하고 그에 따라 개체를 정렬 할 수있는 비교기로 구성됩니다. 목록에 개체를 추가하면 올바른 위치에 삽입됩니다. 비교기에 따라 동일한 개체는이 목록에 추가 된 순서대로 목록에 포함됩니다. 비교기가 비교할 수있는 개체 만 추가합니다.


목록에 이미 비교기에 따라 동일한 개체가 포함되어있는 경우 새 개체가 이러한 다른 개체 바로 뒤에 삽입됩니다.


5
보기에는 좋지만 버그도 있습니다. 두 버전의 addAll에 대한 재정의가 없으므로 목록을 호출 한 후 정렬되지 않습니다.
Tom Anderson

3
그리고 add 메소드는 "효과가 없습니다". 사용할 수없는 경우에는 오히려 UnsupportedOperationException을 발생시켜야합니다.
Thilo

@Tom Anderson @Thilo, 두 사람 모두 동의합니다.
Jigar Joshi

1
흥미롭지 만, 미래의 누군가 addAll()가 모든 요소를 ​​정렬 된 방식으로 사용 하고 생각하는 것을 다소 경계 합니다. UnsupportedOperationException에도 동의합니다.
Chris Knight

1
이 목록에 추가하는 데 따른 시간 복잡도는 얼마입니까?
shrini1000

6

Guava의 TreeMultiSet을 사용해 볼 수 있습니다 .

 Multiset<Integer> ms=TreeMultiset.create(Arrays.asList(1,2,3,1,1,-1,2,4,5,100));
 System.out.println(ms);

+1. 이것은 훌륭한 도서관입니다. MultiSet은A collection that supports order-independent equality, like Set, but may have duplicate elements
Shervin Asgari

5

Aioobe의 접근 방식은 갈 길입니다. 그래도 그의 솔루션에 대해 다음과 같은 개선을 제안하고 싶습니다.

class SortedList<T> extends ArrayList<T> {

    public void insertSorted(T value) {
        int insertPoint = insertPoint(value);
        add(insertPoint, value);
    }

    /**
     * @return The insert point for a new value. If the value is found the insert point can be any
     * of the possible positions that keeps the collection sorted (.33 or 3.3 or 33.).
     */
    private int insertPoint(T key) {
        int low = 0;
        int high = size() - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = (Comparable<T>) get(mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else {
                return mid; // key found
            }
        }

        return low;  // key not found
    }
}

aioobe의 솔루션은 큰 목록을 사용할 때 매우 느려집니다. 목록이 정렬되어 있다는 사실을 사용하면 이진 검색을 사용하여 새 값에 대한 삽입 지점을 찾을 수 있습니다.

나는 또한 상속보다 컴포지션을 사용합니다.

SortedList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

4

목록은 일반적으로 항목이 추가되는 순서를 유지합니다. 확실히 목록이 필요합니까 , 아니면 정렬 된 집합 (예 TreeSet<E>:)이 괜찮습니까? 기본적으로 중복을 보존해야합니까?


2
Jon 감사합니다.하지만 중복을 보존해야합니다
Chris Knight


1

ArrayList의 하위 클래스를 만들고, 요소가 추가 된 후 Collections.sort (this)를 호출 할 수 있습니다. 이렇게하려면 add의 두 가지 버전과 addAll의 두 가지 버전을 재정의해야합니다.

성능은 올바른 위치에 요소를 삽입하는 더 스마트 한 구현만큼 좋지는 않지만 작업을 수행합니다. 목록에 추가하는 것이 드문 경우 목록에있는 모든 작업에 대해 상각되는 비용이 낮아야합니다.


1

다음과 같이 새 클래스를 만드십시오.

public class SortedList<T> extends ArrayList<T> {

private final Comparator<? super T> comparator;

public SortedList() {
    super();
    this.comparator = null;
}

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

@Override
public boolean add(T item) {
    int index = comparator == null ? Collections.binarySearch((List<? extends Comparable<? super T>>)this, item) :
            Collections.binarySearch(this, item, comparator);
    if (index < 0) {
        index = index * -1 - 2;
    }
    super.add(index+1, item);
    return true;
}

@Override
public void add(int index, T item) {
    throw new UnsupportedOperationException("'add' with an index is not supported in SortedArrayList");
}

@Override
public boolean addAll(Collection<? extends T> items) {
    boolean allAdded = true;
    for (T item : items) {
        allAdded = allAdded && add(item);
    }
    return allAdded;
}

@Override
public boolean addAll(int index, Collection<? extends T> items) {
    throw new UnsupportedOperationException("'addAll' with an index is not supported in SortedArrayList");
}

}

다음과 같이 테스트 할 수 있습니다.

    List<Integer> list = new SortedArrayList<>((Integer i1, Integer i2) -> i1.compareTo(i2));
    for (Integer i : Arrays.asList(4, 7, 3, 8, 9, 25, 20, 23, 52, 3)) {
        list.add(i);
    }
    System.out.println(list);

0

SortedSets / Lists와 '일반'정렬 가능한 컬렉션 사이의 선택은 프레젠테이션 목적으로 만 정렬이 필요한지 런타임 동안 거의 모든 지점에서 정렬이 필요한지 여부에 따라 달라집니다. 정렬 된 컬렉션을 사용하면 요소를 삽입 할 때마다 정렬이 수행되기 때문에 훨씬 더 비쌀 수 있습니다.

JDK에서 컬렉션을 선택할 수없는 경우 Apache Commons Collections를 살펴볼 수 있습니다.


0

Collection API를 깨서 정렬 된 목록을 구현하는 현재 제안 된 구현은 트리 또는 유사한 구현을 자체적으로 가지고 있기 때문에 TreeMap을 기반으로 한 구현이 어떻게 수행되는지 궁금합니다. (특히 TreeSet도 TreeMap을 기반으로하기 때문에)

누군가가 그것에 관심이 있다면 자유롭게 조사 할 수 있습니다.

TreeList

핵심 라이브러리 의 일부 이며 물론 Maven 종속성을 통해 추가 할 수 있습니다. (Apache 라이선스)

현재 구현은 구아바 SortedMultiSet 및 Apache Commons 라이브러리의 TreeList와 동일한 수준에서 매우 잘 비교되는 것 같습니다.

그러나 중요한 것을 놓치지 않았는지 확인하기 위해 나만이 구현을 테스트한다면 기쁠 것입니다.

친애하는!


0

나는 같은 문제가 있었다. 그래서 java.util.TreeMap의 소스 코드를 가져와 IndexedTreeMap을 작성 했습니다 . 내 자신의 IndexedNavigableMap을 구현합니다 .

public interface IndexedNavigableMap<K, V> extends NavigableMap<K, V> {
   K exactKey(int index);
   Entry<K, V> exactEntry(int index);
   int keyIndex(K k);
}

구현은 변경 될 때 빨강-검정 트리의 업데이트 노드 가중치를 기반으로합니다. 가중치는 주어진 노드 아래에있는 자식 노드의 수에 자신을 더한 것입니다. 예를 들어 나무가 왼쪽으로 회전하는 경우 :

    private void rotateLeft(Entry<K, V> p) {
    if (p != null) {
        Entry<K, V> r = p.right;

        int delta = getWeight(r.left) - getWeight(p.right);
        p.right = r.left;
        p.updateWeight(delta);

        if (r.left != null) {
            r.left.parent = p;
        }

        r.parent = p.parent;


        if (p.parent == null) {
            root = r;
        } else if (p.parent.left == p) {
            delta = getWeight(r) - getWeight(p.parent.left);
            p.parent.left = r;
            p.parent.updateWeight(delta);
        } else {
            delta = getWeight(r) - getWeight(p.parent.right);
            p.parent.right = r;
            p.parent.updateWeight(delta);
        }

        delta = getWeight(p) - getWeight(r.left);
        r.left = p;
        r.updateWeight(delta);

        p.parent = r;
    }
  }

updateWeight는 단순히 가중치를 루트까지 업데이트합니다.

   void updateWeight(int delta) {
        weight += delta;
        Entry<K, V> p = parent;
        while (p != null) {
            p.weight += delta;
            p = p.parent;
        }
    }

인덱스로 요소를 찾아야 할 때 가중치를 사용하는 구현은 다음과 같습니다.

public K exactKey(int index) {
    if (index < 0 || index > size() - 1) {
        throw new ArrayIndexOutOfBoundsException();
    }
    return getExactKey(root, index);
}

private K getExactKey(Entry<K, V> e, int index) {
    if (e.left == null && index == 0) {
        return e.key;
    }
    if (e.left == null && e.right == null) {
        return e.key;
    }
    if (e.left != null && e.left.weight > index) {
        return getExactKey(e.left, index);
    }
    if (e.left != null && e.left.weight == index) {
        return e.key;
    }
    return getExactKey(e.right, index - (e.left == null ? 0 : e.left.weight) - 1);
}

또한 키의 색인을 찾는 데 매우 편리합니다.

    public int keyIndex(K key) {
    if (key == null) {
        throw new NullPointerException();
    }
    Entry<K, V> e = getEntry(key);
    if (e == null) {
        throw new NullPointerException();
    }
    if (e == root) {
        return getWeight(e) - getWeight(e.right) - 1;//index to return
    }
    int index = 0;
    int cmp;
    index += getWeight(e.left);

    Entry<K, V> p = e.parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        while (p != null) {
            cmp = cpr.compare(key, p.key);
            if (cmp > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    } else {
        Comparable<? super K> k = (Comparable<? super K>) key;
        while (p != null) {
            if (k.compareTo(p.key) > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    }
    return index;
}

이 작업의 결과는 http://code.google.com/p/indexed-tree-map/ 에서 찾을 수 있습니다 .

TreeSet / TreeMap (및 indexed-tree-map 프로젝트의 색인화 된 대응 항목)은 중복 키를 허용하지 않습니다. 값 배열에 1 개의 키를 사용할 수 있습니다. 중복 된 SortedSet이 필요한 경우 값이있는 TreeMap을 배열로 사용합니다. 그렇게 할 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.