Javaでソートされた配列リスト


85

私はこれに対する迅速な答えを見つけることができないことに困惑しています。私は基本的に、java.util.Listインターフェイスを実装するが、そのメンバーをソートされた順序で格納するJavaのデータ構造を探しています。ノーマルArrayListを使用Collections.sort()して使用できることは知っていますが、リストからメンバーを追加したり、頻繁に取得したりするシナリオがあり、メンバーを取得するたびに並べ替える必要はありません。新しいものが追加されました。JDKやサードパーティのライブラリに存在するようなものを誰かに教えてもらえますか?

編集:データ構造は重複を保持する必要があります。

回答の要約:私はこれらすべてが非常に興味深く、多くのことを学びました。特にAioobeは、上記の要件(主に、重複をサポートするソートされたjava.util.List実装)を達成しようとする彼の忍耐力について言及する価値があります。私は彼の答えを私が尋ねたものに対して最も正確であり、私が尋ねたものが正確に私が必要としたものでなかったとしても、私が探していたものの意味を刺激するものとして最も考えました。

私が求めたものの問題は、リストインターフェイス自体とインターフェイスのオプションメソッドの概念にあります。javadocを引用するには:

このインターフェイスのユーザーは、リストのどこに各要素を挿入するかを正確に制御できます。

ソートされたリストへの挿入では、挿入ポイントを正確に制御することはできません。次に、いくつかのメソッドをどのように処理するかを考える必要があります。テイクadd例えば:

public boolean add(Object o)

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

これで、1)コントラクトを破り、addのソートされたバージョンを実装する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 2010年

回答:


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を呼び出すと、ソートされていない方法でメンバーが追加されます。
クリスナイト

はい。それらをリストに追加する以外は、Listインターフェイスに直接違反します。私の更新された答えを参照してください。
aioobe 2010年

@aioobe良い点。しかし、インターフェイスメソッドのサポートされていない操作はコードの臭いではありませんか?適切な方法は、ArrayListを拡張せずにListを実装することかもしれませんが、それでもListはこの目的のためのものではなかったのかもしれません。Javadoc for Listから:The user of this interface has precise control over where in the list each element is insertedこれは、ソートされた方法で要素を挿入するための最良の説明ではなく、それでもadd(int index, Object obj)インターフェースメソッドを処理する必要があります。これらの問題は、リストがソートされた方法で実装されていない理由をおそらく説明しています。
クリスナイト

まあ、操作は理由のためにオプションです。.addSortedArrayListで実行しているときにUnsupportedExceptionOperationを取得しても、驚かないでしょう。はい、同じ理由がaddの両方のバージョン、addAllとsetの両方のバージョンに当てはまります。(これらはすべて、リストインターフェイスによるオプションの操作です。)
aioobe 2010年

ああ、それがオプションの操作だとは思いませんでした。プロットが厚くなる...;)
クリスナイト

10

を使用しjava.util.PriorityQueueます。


7
これはリストではありません。つまり、ランダムアクセスはありません。
Thilo 2010年

1
これはキューベースの優先度ヒープであり、リストを実装していません。
zengr 2010年

3
もちろん、ソート順を維持するリストでは、インデックスは常に変更されるため、ランダムアクセスはおそらく必要ありません。
Thilo 2010年

5
@Qwerky、正確な答えが常に最良の答えであるとは限らないこと、またはOPが実際に求めている答えであることに注意してください。
aioobe 2010年

3
優先キューは、反復時にソートされた順序を許可しません。
マルコロッシ2011年

6

見ていSortedListのを

このクラスは、ソートされたリストを実装します。これは、2つのオブジェクトを比較し、それに応じてオブジェクトをソートできるコンパレータで構成されています。リストにオブジェクトを追加すると、正しい場所に挿入されます。コンパレータによると等しいオブジェクトは、このリストに追加された順序でリストに含まれます。コンパレータが比較できるオブジェクトのみを追加します。


コンパレータによると等しいオブジェクトがリストにすでに含まれている場合、新しいオブジェクトはこれらの他のオブジェクトの直後に挿入されます。


5
それは良さそうに見えますが、バグもあります。どちらのバージョンのaddAllもオーバーライドされないため、それらを呼び出した後、リストはソートされません。
トムアンダーソン

3
そして、addメソッドは「効果がありません」。使用できない場合は、UnsupportedOperationExceptionをスローする必要があります。
Thilo 2010年

@トムアンダーソン@ティロ、あなたの両方に同意します。
jmj 2010年

1
興味深いですが、私は将来誰かがaddAll()それをすべての要素を分類された方法で使用して考えることをかなり警戒しています。UnsupportedOperationExceptionにも同意します。
クリスナイト

1
このリストへの追加の時間計算量はどれくらいですか?
shrini1000 2012年

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。これは素晴らしいライブラリです。マルチセットはA collection that supports order-independent equality, like Set, but may have duplicate elements
Shervin Asgari 2010年

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)を呼び出すことができます。これを行うには、2つのバージョンのaddと2つの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のコモンズコレクション


0

コレクションAPIを壊してソートされたリストを実装する現在提案されている実装には、ツリーなどの独自の実装があるため、TreeMapに基づく実装がどのように実行されるかについて興味がありました。(特に、TreeSetはTreeMapにも基づいているため)

誰かがそれに興味を持っているなら、彼または彼女はそれを自由に調べることができます:

TreeList

コアライブラリの一部もちろんMaven依存関係を介して追加できます。(Apacheライセンス)

現在、実装はguavaのSortedMultiSetおよびApacheCommonsライブラリの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);
}

実装は、変更されたときに赤黒木でノードの重みを更新することに基づいています。重みは、特定のノードの下にある子ノードの数に1を加えたものです。たとえば、木が左に回転した場合:

    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.